Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 : /* This Source Code Form is subject to the terms of the Mozilla Public
4 : * License, v. 2.0. If a copy of the MPL was not distributed with this
5 : * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 :
7 : /* rendering object for textual content of elements */
8 :
9 : #include "nsTextFrame.h"
10 :
11 : #include "gfx2DGlue.h"
12 : #include "gfxPrefs.h"
13 : #include "gfxUtils.h"
14 : #include "mozilla/Attributes.h"
15 : #include "mozilla/ComputedStyle.h"
16 : #include "mozilla/DebugOnly.h"
17 : #include "mozilla/gfx/2D.h"
18 : #include "mozilla/Likely.h"
19 : #include "mozilla/MathAlgorithms.h"
20 : #include "mozilla/TextEvents.h"
21 : #include "mozilla/BinarySearch.h"
22 : #include "mozilla/IntegerRange.h"
23 : #include "mozilla/Unused.h"
24 : #include "mozilla/PodOperations.h"
25 :
26 : #include "nsCOMPtr.h"
27 : #include "nsBlockFrame.h"
28 : #include "nsFontMetrics.h"
29 : #include "nsSplittableFrame.h"
30 : #include "nsLineLayout.h"
31 : #include "nsString.h"
32 : #include "nsUnicharUtils.h"
33 : #include "nsPresContext.h"
34 : #include "nsIContent.h"
35 : #include "nsStyleConsts.h"
36 : #include "nsStyleStruct.h"
37 : #include "nsStyleStructInlines.h"
38 : #include "SVGTextFrame.h"
39 : #include "nsCoord.h"
40 : #include "gfxContext.h"
41 : #include "nsIPresShell.h"
42 : #include "nsTArray.h"
43 : #include "nsCSSPseudoElements.h"
44 : #include "nsCSSFrameConstructor.h"
45 : #include "nsCompatibility.h"
46 : #include "nsCSSColorUtils.h"
47 : #include "nsLayoutUtils.h"
48 : #include "nsDisplayList.h"
49 : #include "nsFrame.h"
50 : #include "nsIMathMLFrame.h"
51 : #include "nsPlaceholderFrame.h"
52 : #include "nsTextFrameUtils.h"
53 : #include "nsTextRunTransformations.h"
54 : #include "MathMLTextRunFactory.h"
55 : #include "nsUnicodeProperties.h"
56 : #include "nsStyleUtil.h"
57 : #include "nsRubyFrame.h"
58 : #include "TextDrawTarget.h"
59 :
60 : #include "nsTextFragment.h"
61 : #include "nsGkAtoms.h"
62 : #include "nsFrameSelection.h"
63 : #include "nsRange.h"
64 : #include "nsCSSRendering.h"
65 : #include "nsContentUtils.h"
66 : #include "nsLineBreaker.h"
67 : #include "nsIFrameInlines.h"
68 : #include "mozilla/intl/WordBreaker.h"
69 : #include "mozilla/ServoStyleSet.h"
70 : #include "mozilla/layers/LayersMessages.h"
71 : #include "mozilla/layers/WebRenderLayerManager.h"
72 : #include "mozilla/layers/WebRenderBridgeChild.h"
73 : #include "mozilla/webrender/WebRenderAPI.h"
74 : #include "mozilla/layers/StackingContextHelper.h"
75 :
76 : #include <algorithm>
77 : #include <limits>
78 : #ifdef ACCESSIBILITY
79 : #include "nsAccessibilityService.h"
80 : #endif
81 :
82 : #include "nsPrintfCString.h"
83 :
84 : #include "gfxContext.h"
85 : #include "mozilla/gfx/DrawTargetRecording.h"
86 :
87 : #include "mozilla/UniquePtr.h"
88 : #include "mozilla/dom/Element.h"
89 : #include "mozilla/LookAndFeel.h"
90 :
91 : #include "GeckoProfiler.h"
92 :
93 : #ifdef DEBUG
94 : #undef NOISY_REFLOW
95 : #undef NOISY_TRIM
96 : #else
97 : #undef NOISY_REFLOW
98 : #undef NOISY_TRIM
99 : #endif
100 :
101 : #ifdef DrawText
102 : #undef DrawText
103 : #endif
104 :
105 : using namespace mozilla;
106 : using namespace mozilla::dom;
107 : using namespace mozilla::gfx;
108 : using namespace mozilla::layers;
109 :
110 : typedef mozilla::layout::TextDrawTarget TextDrawTarget;
111 :
112 : struct TabWidth {
113 : TabWidth(uint32_t aOffset, uint32_t aWidth)
114 0 : : mOffset(aOffset), mWidth(float(aWidth))
115 : { }
116 :
117 : uint32_t mOffset; // DOM offset relative to the current frame's offset.
118 : float mWidth; // extra space to be added at this position (in app units)
119 : };
120 :
121 0 : struct TabWidthStore {
122 : explicit TabWidthStore(int32_t aValidForContentOffset)
123 0 : : mLimit(0)
124 0 : , mValidForContentOffset(aValidForContentOffset)
125 : { }
126 :
127 : // Apply tab widths to the aSpacing array, which corresponds to characters
128 : // beginning at aOffset and has length aLength. (Width records outside this
129 : // range will be ignored.)
130 : void ApplySpacing(gfxTextRun::PropertyProvider::Spacing *aSpacing,
131 : uint32_t aOffset, uint32_t aLength);
132 :
133 : // Offset up to which tabs have been measured; positions beyond this have not
134 : // been calculated yet but may be appended if needed later. It's a DOM
135 : // offset relative to the current frame's offset.
136 : uint32_t mLimit;
137 :
138 : // Need to recalc tab offsets if frame content offset differs from this.
139 : int32_t mValidForContentOffset;
140 :
141 : // A TabWidth record for each tab character measured so far.
142 : nsTArray<TabWidth> mWidths;
143 : };
144 :
145 : namespace {
146 :
147 : struct TabwidthAdaptor
148 : {
149 : const nsTArray<TabWidth>& mWidths;
150 : explicit TabwidthAdaptor(const nsTArray<TabWidth>& aWidths)
151 0 : : mWidths(aWidths) {}
152 0 : uint32_t operator[](size_t aIdx) const {
153 0 : return mWidths[aIdx].mOffset;
154 : }
155 : };
156 :
157 : } // namespace
158 :
159 : void
160 0 : TabWidthStore::ApplySpacing(gfxTextRun::PropertyProvider::Spacing *aSpacing,
161 : uint32_t aOffset, uint32_t aLength)
162 : {
163 0 : size_t i = 0;
164 0 : const size_t len = mWidths.Length();
165 :
166 : // If aOffset is non-zero, do a binary search to find where to start
167 : // processing the tab widths, in case the list is really long. (See bug
168 : // 953247.)
169 : // We need to start from the first entry where mOffset >= aOffset.
170 0 : if (aOffset > 0) {
171 0 : mozilla::BinarySearch(TabwidthAdaptor(mWidths), 0, len, aOffset, &i);
172 : }
173 :
174 0 : uint32_t limit = aOffset + aLength;
175 0 : while (i < len) {
176 0 : const TabWidth& tw = mWidths[i];
177 0 : if (tw.mOffset >= limit) {
178 : break;
179 : }
180 0 : aSpacing[tw.mOffset - aOffset].mAfter += tw.mWidth;
181 0 : i++;
182 : }
183 0 : }
184 :
185 : NS_DECLARE_FRAME_PROPERTY_DELETABLE(TabWidthProperty, TabWidthStore)
186 :
187 : NS_DECLARE_FRAME_PROPERTY_WITHOUT_DTOR(OffsetToFrameProperty, nsTextFrame)
188 :
189 : NS_DECLARE_FRAME_PROPERTY_RELEASABLE(UninflatedTextRunProperty, gfxTextRun)
190 :
191 : NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(FontSizeInflationProperty, float)
192 :
193 : /**
194 : * A glyph observer for the change of a font glyph in a text run.
195 : *
196 : * This is stored in {Simple, Complex}TextRunUserData.
197 : */
198 0 : class GlyphObserver : public gfxFont::GlyphChangeObserver {
199 : public:
200 0 : GlyphObserver(gfxFont* aFont, gfxTextRun* aTextRun)
201 0 : : gfxFont::GlyphChangeObserver(aFont), mTextRun(aTextRun) {
202 0 : MOZ_ASSERT(aTextRun->GetUserData());
203 0 : }
204 : virtual void NotifyGlyphsChanged() override;
205 : private:
206 : gfxTextRun* mTextRun;
207 : };
208 :
209 0 : static const nsFrameState TEXT_REFLOW_FLAGS =
210 : TEXT_FIRST_LETTER |
211 : TEXT_START_OF_LINE |
212 : TEXT_END_OF_LINE |
213 : TEXT_HYPHEN_BREAK |
214 : TEXT_TRIMMED_TRAILING_WHITESPACE |
215 : TEXT_JUSTIFICATION_ENABLED |
216 : TEXT_HAS_NONCOLLAPSED_CHARACTERS |
217 0 : TEXT_SELECTION_UNDERLINE_OVERFLOWED |
218 : TEXT_NO_RENDERED_GLYPHS;
219 :
220 0 : static const nsFrameState TEXT_WHITESPACE_FLAGS =
221 0 : TEXT_IS_ONLY_WHITESPACE |
222 : TEXT_ISNOT_ONLY_WHITESPACE;
223 :
224 : /*
225 : * Some general notes
226 : *
227 : * Text frames delegate work to gfxTextRun objects. The gfxTextRun object
228 : * transforms text to positioned glyphs. It can report the geometry of the
229 : * glyphs and paint them. Text frames configure gfxTextRuns by providing text,
230 : * spacing, language, and other information.
231 : *
232 : * A gfxTextRun can cover more than one DOM text node. This is necessary to
233 : * get kerning, ligatures and shaping for text that spans multiple text nodes
234 : * but is all the same font.
235 : *
236 : * The userdata for a gfxTextRun object can be:
237 : *
238 : * - A nsTextFrame* in the case a text run maps to only one flow. In this
239 : * case, the textrun's user data pointer is a pointer to mStartFrame for that
240 : * flow, mDOMOffsetToBeforeTransformOffset is zero, and mContentLength is the
241 : * length of the text node.
242 : *
243 : * - A SimpleTextRunUserData in the case a text run maps to one flow, but we
244 : * still have to keep a list of glyph observers.
245 : *
246 : * - A ComplexTextRunUserData in the case a text run maps to multiple flows,
247 : * but we need to keep a list of glyph observers.
248 : *
249 : * - A TextRunUserData in the case a text run maps multiple flows, but it
250 : * doesn't have any glyph observer for changes in SVG fonts.
251 : *
252 : * You can differentiate between the four different cases with the
253 : * TEXT_IS_SIMPLE_FLOW and TEXT_MIGHT_HAVE_GLYPH_CHANGES flags.
254 : *
255 : * We go to considerable effort to make sure things work even if in-flow
256 : * siblings have different ComputedStyles (i.e., first-letter and first-line).
257 : *
258 : * Our convention is that unsigned integer character offsets are offsets into
259 : * the transformed string. Signed integer character offsets are offsets into
260 : * the DOM string.
261 : *
262 : * XXX currently we don't handle hyphenated breaks between text frames where the
263 : * hyphen occurs at the end of the first text frame, e.g.
264 : * <b>Kit­</b>ty
265 : */
266 :
267 : /**
268 : * This is our user data for the textrun, when textRun->GetFlags2() has
269 : * TEXT_IS_SIMPLE_FLOW set, and also TEXT_MIGHT_HAVE_GLYPH_CHANGES.
270 : *
271 : * This allows having an array of observers if there are fonts whose glyphs
272 : * might change, but also avoid allocation in the simple case that there aren't.
273 : */
274 0 : struct SimpleTextRunUserData {
275 : nsTArray<UniquePtr<GlyphObserver>> mGlyphObservers;
276 : nsTextFrame* mFrame;
277 : explicit SimpleTextRunUserData(nsTextFrame* aFrame)
278 0 : : mFrame(aFrame)
279 : {
280 : }
281 : };
282 :
283 : /**
284 : * We use an array of these objects to record which text frames
285 : * are associated with the textrun. mStartFrame is the start of a list of
286 : * text frames. Some sequence of its continuations are covered by the textrun.
287 : * A content textnode can have at most one TextRunMappedFlow associated with it
288 : * for a given textrun.
289 : *
290 : * mDOMOffsetToBeforeTransformOffset is added to DOM offsets for those frames to obtain
291 : * the offset into the before-transformation text of the textrun. It can be
292 : * positive (when a text node starts in the middle of a text run) or
293 : * negative (when a text run starts in the middle of a text node). Of course
294 : * it can also be zero.
295 : */
296 : struct TextRunMappedFlow {
297 : nsTextFrame* mStartFrame;
298 : int32_t mDOMOffsetToBeforeTransformOffset;
299 : // The text mapped starts at mStartFrame->GetContentOffset() and is this long
300 : uint32_t mContentLength;
301 : };
302 :
303 : /**
304 : * This is the type in the gfxTextRun's userdata field in the common case that
305 : * the text run maps to multiple flows, but no fonts have been found with
306 : * animatable glyphs.
307 : *
308 : * This way, we avoid allocating and constructing the extra nsTArray.
309 : */
310 : struct TextRunUserData {
311 : #ifdef DEBUG
312 : TextRunMappedFlow* mMappedFlows;
313 : #endif
314 : uint32_t mMappedFlowCount;
315 : uint32_t mLastFlowIndex;
316 : };
317 :
318 : /**
319 : * This is our user data for the textrun, when textRun->GetFlags2() does not
320 : * have TEXT_IS_SIMPLE_FLOW set and has the TEXT_MIGHT HAVE_GLYPH_CHANGES flag.
321 : */
322 0 : struct ComplexTextRunUserData : public TextRunUserData {
323 : nsTArray<UniquePtr<GlyphObserver>> mGlyphObservers;
324 : };
325 :
326 : /**
327 : * This helper object computes colors used for painting, and also IME
328 : * underline information. The data is computed lazily and cached as necessary.
329 : * These live for just the duration of one paint operation.
330 : */
331 0 : class nsTextPaintStyle {
332 : public:
333 : explicit nsTextPaintStyle(nsTextFrame* aFrame);
334 :
335 : void SetResolveColors(bool aResolveColors) {
336 0 : mResolveColors = aResolveColors;
337 : }
338 :
339 : nscolor GetTextColor();
340 :
341 : // SVG text has its own painting process, so we should never get its stroke
342 : // property from here.
343 0 : nscolor GetWebkitTextStrokeColor() {
344 0 : if (nsSVGUtils::IsInSVGTextSubtree(mFrame)) {
345 : return 0;
346 : }
347 0 : return mFrame->StyleText()->mWebkitTextStrokeColor.CalcColor(mFrame);
348 : }
349 0 : float GetWebkitTextStrokeWidth() {
350 0 : if (nsSVGUtils::IsInSVGTextSubtree(mFrame)) {
351 : return 0.0f;
352 : }
353 0 : nscoord coord = mFrame->StyleText()->mWebkitTextStrokeWidth;
354 0 : return mFrame->PresContext()->AppUnitsToFloatDevPixels(coord);
355 : }
356 :
357 : /**
358 : * Compute the colors for normally-selected text. Returns false if
359 : * the normal selection is not being displayed.
360 : */
361 : bool GetSelectionColors(nscolor* aForeColor,
362 : nscolor* aBackColor);
363 : void GetHighlightColors(nscolor* aForeColor,
364 : nscolor* aBackColor);
365 : void GetURLSecondaryColor(nscolor* aForeColor);
366 : void GetIMESelectionColors(int32_t aIndex,
367 : nscolor* aForeColor,
368 : nscolor* aBackColor);
369 : // if this returns false, we don't need to draw underline.
370 : bool GetSelectionUnderlineForPaint(int32_t aIndex,
371 : nscolor* aLineColor,
372 : float* aRelativeSize,
373 : uint8_t* aStyle);
374 :
375 : // if this returns false, we don't need to draw underline.
376 : static bool GetSelectionUnderline(nsPresContext* aPresContext,
377 : int32_t aIndex,
378 : nscolor* aLineColor,
379 : float* aRelativeSize,
380 : uint8_t* aStyle);
381 :
382 : // if this returns false, no text-shadow was specified for the selection
383 : // and the *aShadow parameter was not modified.
384 : bool GetSelectionShadow(nsCSSShadowArray** aShadow);
385 :
386 : nsPresContext* PresContext() const { return mPresContext; }
387 :
388 : enum {
389 : eIndexRawInput = 0,
390 : eIndexSelRawText,
391 : eIndexConvText,
392 : eIndexSelConvText,
393 : eIndexSpellChecker
394 : };
395 :
396 0 : static int32_t GetUnderlineStyleIndexForSelectionType(
397 : SelectionType aSelectionType)
398 : {
399 0 : switch (aSelectionType) {
400 : case SelectionType::eIMERawClause:
401 : return eIndexRawInput;
402 : case SelectionType::eIMESelectedRawClause:
403 0 : return eIndexSelRawText;
404 : case SelectionType::eIMEConvertedClause:
405 0 : return eIndexConvText;
406 : case SelectionType::eIMESelectedClause:
407 0 : return eIndexSelConvText;
408 : case SelectionType::eSpellCheck:
409 0 : return eIndexSpellChecker;
410 : default:
411 0 : NS_WARNING("non-IME selection type");
412 0 : return eIndexRawInput;
413 : }
414 : }
415 :
416 : nscolor GetSystemFieldForegroundColor();
417 : nscolor GetSystemFieldBackgroundColor();
418 :
419 : protected:
420 : nsTextFrame* mFrame;
421 : nsPresContext* mPresContext;
422 : bool mInitCommonColors;
423 : bool mInitSelectionColorsAndShadow;
424 : bool mResolveColors;
425 :
426 : // Selection data
427 :
428 : int16_t mSelectionStatus; // see nsIDocument.h SetDisplaySelection()
429 : nscolor mSelectionTextColor;
430 : nscolor mSelectionBGColor;
431 : RefPtr<nsCSSShadowArray> mSelectionShadow;
432 : bool mHasSelectionShadow;
433 :
434 : // Common data
435 :
436 : int32_t mSufficientContrast;
437 : nscolor mFrameBackgroundColor;
438 : nscolor mSystemFieldForegroundColor;
439 : nscolor mSystemFieldBackgroundColor;
440 :
441 : // selection colors and underline info, the colors are resolved colors if
442 : // mResolveColors is true (which is the default), i.e., the foreground color
443 : // and background color are swapped if it's needed. And also line color will
444 : // be resolved from them.
445 : struct nsSelectionStyle {
446 : bool mInit;
447 : nscolor mTextColor;
448 : nscolor mBGColor;
449 : nscolor mUnderlineColor;
450 : uint8_t mUnderlineStyle;
451 : float mUnderlineRelativeSize;
452 : };
453 : nsSelectionStyle mSelectionStyle[5];
454 :
455 : // Color initializations
456 : void InitCommonColors();
457 : bool InitSelectionColorsAndShadow();
458 :
459 : nsSelectionStyle* GetSelectionStyle(int32_t aIndex);
460 : void InitSelectionStyle(int32_t aIndex);
461 :
462 : bool EnsureSufficientContrast(nscolor *aForeColor, nscolor *aBackColor);
463 :
464 : nscolor GetResolvedForeColor(nscolor aColor, nscolor aDefaultForeColor,
465 : nscolor aBackColor);
466 : };
467 :
468 : static TextRunUserData*
469 0 : CreateUserData(uint32_t aMappedFlowCount)
470 : {
471 : TextRunUserData* data = static_cast<TextRunUserData*>
472 0 : (moz_xmalloc(sizeof(TextRunUserData) +
473 0 : aMappedFlowCount * sizeof(TextRunMappedFlow)));
474 : #ifdef DEBUG
475 0 : data->mMappedFlows = reinterpret_cast<TextRunMappedFlow*>(data + 1);
476 : #endif
477 0 : data->mMappedFlowCount = aMappedFlowCount;
478 0 : data->mLastFlowIndex = 0;
479 0 : return data;
480 : }
481 :
482 : static void
483 0 : DestroyUserData(TextRunUserData* aUserData)
484 : {
485 0 : if (aUserData) {
486 0 : free(aUserData);
487 : }
488 0 : }
489 :
490 : static ComplexTextRunUserData*
491 0 : CreateComplexUserData(uint32_t aMappedFlowCount)
492 : {
493 : ComplexTextRunUserData* data = static_cast<ComplexTextRunUserData*>
494 0 : (moz_xmalloc(sizeof(ComplexTextRunUserData) +
495 0 : aMappedFlowCount * sizeof(TextRunMappedFlow)));
496 0 : new (data) ComplexTextRunUserData();
497 : #ifdef DEBUG
498 0 : data->mMappedFlows = reinterpret_cast<TextRunMappedFlow*>(data + 1);
499 : #endif
500 0 : data->mMappedFlowCount = aMappedFlowCount;
501 0 : data->mLastFlowIndex = 0;
502 0 : return data;
503 : }
504 :
505 : static void
506 0 : DestroyComplexUserData(ComplexTextRunUserData* aUserData)
507 : {
508 0 : if (aUserData) {
509 0 : aUserData->~ComplexTextRunUserData();
510 0 : free(aUserData);
511 : }
512 0 : }
513 :
514 : static void
515 0 : DestroyTextRunUserData(gfxTextRun* aTextRun)
516 : {
517 0 : MOZ_ASSERT(aTextRun->GetUserData());
518 0 : if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_IS_SIMPLE_FLOW) {
519 0 : if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_MIGHT_HAVE_GLYPH_CHANGES) {
520 0 : delete static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData());
521 : }
522 : } else {
523 0 : if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_MIGHT_HAVE_GLYPH_CHANGES) {
524 : DestroyComplexUserData(
525 0 : static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData()));
526 : } else {
527 : DestroyUserData(
528 0 : static_cast<TextRunUserData*>(aTextRun->GetUserData()));
529 : }
530 : }
531 0 : aTextRun->ClearFlagBits(nsTextFrameUtils::Flags::TEXT_MIGHT_HAVE_GLYPH_CHANGES);
532 0 : aTextRun->SetUserData(nullptr);
533 0 : }
534 :
535 : static TextRunMappedFlow*
536 0 : GetMappedFlows(const gfxTextRun* aTextRun)
537 : {
538 0 : MOZ_ASSERT(aTextRun->GetUserData(), "UserData must exist.");
539 0 : MOZ_ASSERT(!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_IS_SIMPLE_FLOW),
540 : "The method should not be called for simple flows.");
541 : TextRunMappedFlow* flows;
542 0 : if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_MIGHT_HAVE_GLYPH_CHANGES) {
543 0 : flows = reinterpret_cast<TextRunMappedFlow*>(
544 0 : static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData()) + 1);
545 : } else {
546 0 : flows = reinterpret_cast<TextRunMappedFlow*>(
547 0 : static_cast<TextRunUserData*>(aTextRun->GetUserData()) + 1);
548 : }
549 0 : MOZ_ASSERT(static_cast<TextRunUserData*>(aTextRun->GetUserData())->
550 : mMappedFlows == flows,
551 : "GetMappedFlows should return the same pointer as mMappedFlows.");
552 0 : return flows;
553 : }
554 :
555 : /**
556 : * These are utility functions just for helping with the complexity related with
557 : * the text runs user data.
558 : */
559 : static nsTextFrame*
560 0 : GetFrameForSimpleFlow(const gfxTextRun* aTextRun)
561 : {
562 0 : MOZ_ASSERT(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_IS_SIMPLE_FLOW,
563 : "Not so simple flow?");
564 0 : if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_MIGHT_HAVE_GLYPH_CHANGES) {
565 0 : return static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData())->mFrame;
566 : }
567 :
568 0 : return static_cast<nsTextFrame*>(aTextRun->GetUserData());
569 : }
570 :
571 : /**
572 : * Remove |aTextRun| from the frame continuation chain starting at
573 : * |aStartContinuation| if non-null, otherwise starting at |aFrame|.
574 : * Unmark |aFrame| as a text run owner if it's the frame we start at.
575 : * Return true if |aStartContinuation| is non-null and was found
576 : * in the next-continuation chain of |aFrame|.
577 : */
578 : static bool
579 0 : ClearAllTextRunReferences(nsTextFrame* aFrame, gfxTextRun* aTextRun,
580 : nsTextFrame* aStartContinuation,
581 : nsFrameState aWhichTextRunState)
582 : {
583 0 : MOZ_ASSERT(aFrame, "null frame");
584 0 : MOZ_ASSERT(!aStartContinuation ||
585 : (!aStartContinuation->GetTextRun(nsTextFrame::eInflated) ||
586 : aStartContinuation->GetTextRun(nsTextFrame::eInflated) == aTextRun) ||
587 : (!aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) ||
588 : aStartContinuation->GetTextRun(nsTextFrame::eNotInflated) == aTextRun),
589 : "wrong aStartContinuation for this text run");
590 :
591 0 : if (!aStartContinuation || aStartContinuation == aFrame) {
592 0 : aFrame->RemoveStateBits(aWhichTextRunState);
593 : } else {
594 : do {
595 0 : NS_ASSERTION(aFrame->IsTextFrame(), "Bad frame");
596 0 : aFrame = aFrame->GetNextContinuation();
597 0 : } while (aFrame && aFrame != aStartContinuation);
598 : }
599 0 : bool found = aStartContinuation == aFrame;
600 0 : while (aFrame) {
601 0 : NS_ASSERTION(aFrame->IsTextFrame(), "Bad frame");
602 0 : if (!aFrame->RemoveTextRun(aTextRun)) {
603 : break;
604 : }
605 0 : aFrame = aFrame->GetNextContinuation();
606 : }
607 :
608 0 : MOZ_ASSERT(!found || aStartContinuation, "how did we find null?");
609 0 : return found;
610 : }
611 :
612 : /**
613 : * Kill all references to |aTextRun| starting at |aStartContinuation|.
614 : * It could be referenced by any of its owners, and all their in-flows.
615 : * If |aStartContinuation| is null then process all userdata frames
616 : * and their continuations.
617 : * @note the caller is expected to take care of possibly destroying the
618 : * text run if all userdata frames were reset (userdata is deallocated
619 : * by this function though). The caller can detect this has occured by
620 : * checking |aTextRun->GetUserData() == nullptr|.
621 : */
622 : static void
623 0 : UnhookTextRunFromFrames(gfxTextRun* aTextRun, nsTextFrame* aStartContinuation)
624 : {
625 0 : if (!aTextRun->GetUserData()) {
626 : return;
627 : }
628 :
629 0 : if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_IS_SIMPLE_FLOW) {
630 0 : nsTextFrame* userDataFrame = GetFrameForSimpleFlow(aTextRun);
631 : nsFrameState whichTextRunState =
632 0 : userDataFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
633 0 : ? TEXT_IN_TEXTRUN_USER_DATA
634 0 : : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA;
635 : DebugOnly<bool> found =
636 0 : ClearAllTextRunReferences(userDataFrame, aTextRun,
637 0 : aStartContinuation, whichTextRunState);
638 0 : NS_ASSERTION(!aStartContinuation || found,
639 : "aStartContinuation wasn't found in simple flow text run");
640 0 : if (!(userDataFrame->GetStateBits() & whichTextRunState)) {
641 0 : DestroyTextRunUserData(aTextRun);
642 : }
643 : } else {
644 0 : auto userData = static_cast<TextRunUserData*>(aTextRun->GetUserData());
645 0 : TextRunMappedFlow* userMappedFlows = GetMappedFlows(aTextRun);
646 0 : int32_t destroyFromIndex = aStartContinuation ? -1 : 0;
647 0 : for (uint32_t i = 0; i < userData->mMappedFlowCount; ++i) {
648 0 : nsTextFrame* userDataFrame = userMappedFlows[i].mStartFrame;
649 : nsFrameState whichTextRunState =
650 0 : userDataFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
651 0 : ? TEXT_IN_TEXTRUN_USER_DATA
652 0 : : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA;
653 : bool found =
654 : ClearAllTextRunReferences(userDataFrame, aTextRun,
655 0 : aStartContinuation, whichTextRunState);
656 0 : if (found) {
657 0 : if (userDataFrame->GetStateBits() & whichTextRunState) {
658 0 : destroyFromIndex = i + 1;
659 : } else {
660 0 : destroyFromIndex = i;
661 : }
662 : aStartContinuation = nullptr;
663 : }
664 : }
665 0 : NS_ASSERTION(destroyFromIndex >= 0,
666 : "aStartContinuation wasn't found in multi flow text run");
667 0 : if (destroyFromIndex == 0) {
668 0 : DestroyTextRunUserData(aTextRun);
669 : } else {
670 0 : userData->mMappedFlowCount = uint32_t(destroyFromIndex);
671 0 : if (userData->mLastFlowIndex >= uint32_t(destroyFromIndex)) {
672 0 : userData->mLastFlowIndex = uint32_t(destroyFromIndex) - 1;
673 : }
674 : }
675 : }
676 : }
677 :
678 : static void
679 0 : InvalidateFrameDueToGlyphsChanged(nsIFrame* aFrame)
680 : {
681 0 : MOZ_ASSERT(aFrame);
682 :
683 0 : nsIPresShell* shell = aFrame->PresShell();
684 0 : for (nsIFrame* f = aFrame; f;
685 : f = nsLayoutUtils::GetNextContinuationOrIBSplitSibling(f)) {
686 0 : f->InvalidateFrame();
687 :
688 : // If this is a non-display text frame within SVG <text>, we need
689 : // to reflow the SVGTextFrame. (This is similar to reflowing the
690 : // SVGTextFrame in response to style changes, in
691 : // SVGTextFrame::DidSetComputedStyle.)
692 0 : if (nsSVGUtils::IsInSVGTextSubtree(f) &&
693 0 : f->GetStateBits() & NS_FRAME_IS_NONDISPLAY) {
694 : auto svgTextFrame = static_cast<SVGTextFrame*>(
695 0 : nsLayoutUtils::GetClosestFrameOfType(f, LayoutFrameType::SVGText));
696 0 : svgTextFrame->ScheduleReflowSVGNonDisplayText(nsIPresShell::eResize);
697 : } else {
698 : // Theoretically we could just update overflow areas, perhaps using
699 : // OverflowChangedTracker, but that would do a bunch of work eagerly that
700 : // we should probably do lazily here since there could be a lot
701 : // of text frames affected and we'd like to coalesce the work. So that's
702 : // not easy to do well.
703 0 : shell->FrameNeedsReflow(f, nsIPresShell::eResize, NS_FRAME_IS_DIRTY);
704 : }
705 : }
706 0 : }
707 :
708 : void
709 0 : GlyphObserver::NotifyGlyphsChanged()
710 : {
711 0 : if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_IS_SIMPLE_FLOW) {
712 0 : InvalidateFrameDueToGlyphsChanged(GetFrameForSimpleFlow(mTextRun));
713 0 : return;
714 : }
715 :
716 0 : auto data = static_cast<TextRunUserData*>(mTextRun->GetUserData());
717 0 : TextRunMappedFlow* userMappedFlows = GetMappedFlows(mTextRun);
718 0 : for (uint32_t i = 0; i < data->mMappedFlowCount; ++i) {
719 0 : InvalidateFrameDueToGlyphsChanged(userMappedFlows[i].mStartFrame);
720 : }
721 : }
722 :
723 0 : int32_t nsTextFrame::GetContentEnd() const {
724 0 : nsTextFrame* next = GetNextContinuation();
725 0 : return next ? next->GetContentOffset() : mContent->GetText()->GetLength();
726 : }
727 :
728 : struct FlowLengthProperty {
729 : int32_t mStartOffset;
730 : // The offset of the next fixed continuation after mStartOffset, or
731 : // of the end of the text if there is none
732 : int32_t mEndFlowOffset;
733 : };
734 :
735 0 : int32_t nsTextFrame::GetInFlowContentLength() {
736 0 : if (!(mState & NS_FRAME_IS_BIDI)) {
737 0 : return mContent->TextLength() - mContentOffset;
738 : }
739 :
740 : FlowLengthProperty* flowLength =
741 0 : mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)
742 0 : ? static_cast<FlowLengthProperty*>(mContent->GetProperty(nsGkAtoms::flowlength))
743 0 : : nullptr;
744 :
745 : /**
746 : * This frame must start inside the cached flow. If the flow starts at
747 : * mContentOffset but this frame is empty, logically it might be before the
748 : * start of the cached flow.
749 : */
750 0 : if (flowLength &&
751 0 : (flowLength->mStartOffset < mContentOffset ||
752 0 : (flowLength->mStartOffset == mContentOffset && GetContentEnd() > mContentOffset)) &&
753 0 : flowLength->mEndFlowOffset > mContentOffset) {
754 : #ifdef DEBUG
755 0 : NS_ASSERTION(flowLength->mEndFlowOffset >= GetContentEnd(),
756 : "frame crosses fixed continuation boundary");
757 : #endif
758 0 : return flowLength->mEndFlowOffset - mContentOffset;
759 : }
760 :
761 0 : nsTextFrame* nextBidi = LastInFlow()->GetNextContinuation();
762 0 : int32_t endFlow = nextBidi ? nextBidi->GetContentOffset() : mContent->TextLength();
763 :
764 0 : if (!flowLength) {
765 0 : flowLength = new FlowLengthProperty;
766 0 : if (NS_FAILED(mContent->SetProperty(nsGkAtoms::flowlength, flowLength,
767 : nsINode::DeleteProperty<FlowLengthProperty>))) {
768 0 : delete flowLength;
769 0 : flowLength = nullptr;
770 : }
771 0 : mContent->SetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
772 : }
773 0 : if (flowLength) {
774 0 : flowLength->mStartOffset = mContentOffset;
775 0 : flowLength->mEndFlowOffset = endFlow;
776 : }
777 :
778 0 : return endFlow - mContentOffset;
779 : }
780 :
781 : // Smarter versions of dom::IsSpaceCharacter.
782 : // Unicode is really annoying; sometimes a space character isn't whitespace ---
783 : // when it combines with another character
784 : // So we have several versions of IsSpace for use in different contexts.
785 :
786 0 : static bool IsSpaceCombiningSequenceTail(const nsTextFragment* aFrag, uint32_t aPos)
787 : {
788 0 : NS_ASSERTION(aPos <= aFrag->GetLength(), "Bad offset");
789 0 : if (!aFrag->Is2b())
790 : return false;
791 0 : return nsTextFrameUtils::IsSpaceCombiningSequenceTail(
792 0 : aFrag->Get2b() + aPos, aFrag->GetLength() - aPos);
793 : }
794 :
795 : // Check whether aPos is a space for CSS 'word-spacing' purposes
796 : static bool
797 0 : IsCSSWordSpacingSpace(const nsTextFragment* aFrag, uint32_t aPos,
798 : const nsTextFrame* aFrame, const nsStyleText* aStyleText)
799 : {
800 0 : NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");
801 :
802 0 : char16_t ch = aFrag->CharAt(aPos);
803 0 : switch (ch) {
804 : case ' ':
805 : case CH_NBSP:
806 0 : return !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
807 : case '\r':
808 0 : case '\t': return !aStyleText->WhiteSpaceIsSignificant();
809 0 : case '\n': return !aStyleText->NewlineIsSignificant(aFrame);
810 : default: return false;
811 : }
812 : }
813 :
814 : // Check whether the string aChars/aLength starts with space that's
815 : // trimmable according to CSS 'white-space:normal/nowrap'.
816 0 : static bool IsTrimmableSpace(const char16_t* aChars, uint32_t aLength)
817 : {
818 0 : NS_ASSERTION(aLength > 0, "No text for IsSpace!");
819 :
820 0 : char16_t ch = *aChars;
821 0 : if (ch == ' ')
822 0 : return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(aChars + 1, aLength - 1);
823 0 : return ch == '\t' || ch == '\f' || ch == '\n' || ch == '\r';
824 : }
825 :
826 : // Check whether the character aCh is trimmable according to CSS
827 : // 'white-space:normal/nowrap'
828 0 : static bool IsTrimmableSpace(char aCh)
829 : {
830 0 : return aCh == ' ' || aCh == '\t' || aCh == '\f' || aCh == '\n' || aCh == '\r';
831 : }
832 :
833 0 : static bool IsTrimmableSpace(const nsTextFragment* aFrag, uint32_t aPos,
834 : const nsStyleText* aStyleText)
835 : {
836 0 : NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");
837 :
838 0 : switch (aFrag->CharAt(aPos)) {
839 0 : case ' ': return !aStyleText->WhiteSpaceIsSignificant() &&
840 0 : !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
841 0 : case '\n': return !aStyleText->NewlineIsSignificantStyle() &&
842 : aStyleText->mWhiteSpace != mozilla::StyleWhiteSpace::PreSpace;
843 : case '\t':
844 : case '\r':
845 0 : case '\f': return !aStyleText->WhiteSpaceIsSignificant();
846 : default: return false;
847 : }
848 : }
849 :
850 0 : static bool IsSelectionSpace(const nsTextFragment* aFrag, uint32_t aPos)
851 : {
852 0 : NS_ASSERTION(aPos < aFrag->GetLength(), "No text for IsSpace!");
853 0 : char16_t ch = aFrag->CharAt(aPos);
854 0 : if (ch == ' ' || ch == CH_NBSP)
855 0 : return !IsSpaceCombiningSequenceTail(aFrag, aPos + 1);
856 0 : return ch == '\t' || ch == '\n' || ch == '\f' || ch == '\r';
857 : }
858 :
859 : // Count the amount of trimmable whitespace (as per CSS
860 : // 'white-space:normal/nowrap') in a text fragment. The first
861 : // character is at offset aStartOffset; the maximum number of characters
862 : // to check is aLength. aDirection is -1 or 1 depending on whether we should
863 : // progress backwards or forwards.
864 : static uint32_t
865 0 : GetTrimmableWhitespaceCount(const nsTextFragment* aFrag,
866 : int32_t aStartOffset, int32_t aLength,
867 : int32_t aDirection)
868 : {
869 0 : int32_t count = 0;
870 0 : if (aFrag->Is2b()) {
871 0 : const char16_t* str = aFrag->Get2b() + aStartOffset;
872 0 : int32_t fragLen = aFrag->GetLength() - aStartOffset;
873 0 : for (; count < aLength; ++count) {
874 0 : if (!IsTrimmableSpace(str, fragLen))
875 : break;
876 0 : str += aDirection;
877 0 : fragLen -= aDirection;
878 : }
879 : } else {
880 0 : const char* str = aFrag->Get1b() + aStartOffset;
881 0 : for (; count < aLength; ++count) {
882 0 : if (!IsTrimmableSpace(*str))
883 : break;
884 0 : str += aDirection;
885 : }
886 : }
887 0 : return count;
888 : }
889 :
890 : static bool
891 0 : IsAllWhitespace(const nsTextFragment* aFrag, bool aAllowNewline)
892 : {
893 0 : if (aFrag->Is2b())
894 : return false;
895 0 : int32_t len = aFrag->GetLength();
896 0 : const char* str = aFrag->Get1b();
897 0 : for (int32_t i = 0; i < len; ++i) {
898 0 : char ch = str[i];
899 0 : if (ch == ' ' || ch == '\t' || ch == '\r' || (ch == '\n' && aAllowNewline))
900 : continue;
901 : return false;
902 : }
903 : return true;
904 : }
905 :
906 : static void
907 0 : ClearObserversFromTextRun(gfxTextRun* aTextRun)
908 : {
909 0 : if (!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_MIGHT_HAVE_GLYPH_CHANGES)) {
910 : return;
911 : }
912 :
913 0 : if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_IS_SIMPLE_FLOW) {
914 0 : static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData())
915 0 : ->mGlyphObservers.Clear();
916 : } else {
917 0 : static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData())
918 0 : ->mGlyphObservers.Clear();
919 : }
920 : }
921 :
922 : static void
923 0 : CreateObserversForAnimatedGlyphs(gfxTextRun* aTextRun)
924 : {
925 0 : if (!aTextRun->GetUserData()) {
926 0 : return;
927 : }
928 :
929 0 : ClearObserversFromTextRun(aTextRun);
930 :
931 0 : nsTArray<gfxFont*> fontsWithAnimatedGlyphs;
932 : uint32_t numGlyphRuns;
933 : const gfxTextRun::GlyphRun* glyphRuns =
934 0 : aTextRun->GetGlyphRuns(&numGlyphRuns);
935 0 : for (uint32_t i = 0; i < numGlyphRuns; ++i) {
936 0 : gfxFont* font = glyphRuns[i].mFont;
937 0 : if (font->GlyphsMayChange() && !fontsWithAnimatedGlyphs.Contains(font)) {
938 0 : fontsWithAnimatedGlyphs.AppendElement(font);
939 : }
940 : }
941 0 : if (fontsWithAnimatedGlyphs.IsEmpty()) {
942 : // NB: Theoretically, we should clear the TEXT_MIGHT_HAVE_GLYPH_CHANGES
943 : // here. That would involve de-allocating the simple user data struct if
944 : // present too, and resetting the pointer to the frame. In practice, I
945 : // don't think worth doing that work here, given the flag's only purpose is
946 : // to distinguish what kind of user data is there.
947 : return;
948 : }
949 :
950 : nsTArray<UniquePtr<GlyphObserver>>* observers;
951 :
952 0 : if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_IS_SIMPLE_FLOW) {
953 : // Swap the frame pointer for a just-allocated SimpleTextRunUserData if
954 : // appropriate.
955 0 : if (!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_MIGHT_HAVE_GLYPH_CHANGES)) {
956 0 : auto frame = static_cast<nsTextFrame*>(aTextRun->GetUserData());
957 0 : aTextRun->SetUserData(new SimpleTextRunUserData(frame));
958 : }
959 :
960 : auto data =
961 0 : static_cast<SimpleTextRunUserData*>(aTextRun->GetUserData());
962 0 : observers = &data->mGlyphObservers;
963 : } else {
964 0 : if (!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_MIGHT_HAVE_GLYPH_CHANGES)) {
965 0 : auto oldData = static_cast<TextRunUserData*>(aTextRun->GetUserData());
966 0 : TextRunMappedFlow* oldMappedFlows = GetMappedFlows(aTextRun);
967 : ComplexTextRunUserData* data =
968 0 : CreateComplexUserData(oldData->mMappedFlowCount);
969 : TextRunMappedFlow* dataMappedFlows =
970 0 : reinterpret_cast<TextRunMappedFlow*>(data + 1);
971 0 : data->mLastFlowIndex = oldData->mLastFlowIndex;
972 0 : for (uint32_t i = 0; i < oldData->mMappedFlowCount; ++i) {
973 0 : dataMappedFlows[i] = oldMappedFlows[i];
974 : }
975 0 : DestroyUserData(oldData);
976 0 : aTextRun->SetUserData(data);
977 : }
978 0 : auto data = static_cast<ComplexTextRunUserData*>(aTextRun->GetUserData());
979 0 : observers = &data->mGlyphObservers;
980 : }
981 :
982 0 : aTextRun->SetFlagBits(nsTextFrameUtils::Flags::TEXT_MIGHT_HAVE_GLYPH_CHANGES);
983 :
984 0 : for (auto font : fontsWithAnimatedGlyphs) {
985 0 : observers->AppendElement(new GlyphObserver(font, aTextRun));
986 : }
987 : }
988 :
989 : /**
990 : * This class accumulates state as we scan a paragraph of text. It detects
991 : * textrun boundaries (changes from text to non-text, hard
992 : * line breaks, and font changes) and builds a gfxTextRun at each boundary.
993 : * It also detects linebreaker run boundaries (changes from text to non-text,
994 : * and hard line breaks) and at each boundary runs the linebreaker to compute
995 : * potential line breaks. It also records actual line breaks to store them in
996 : * the textruns.
997 : */
998 : class BuildTextRunsScanner {
999 : public:
1000 0 : BuildTextRunsScanner(nsPresContext* aPresContext, DrawTarget* aDrawTarget,
1001 0 : nsIFrame* aLineContainer, nsTextFrame::TextRunType aWhichTextRun) :
1002 : mDrawTarget(aDrawTarget),
1003 : mLineContainer(aLineContainer),
1004 : mCommonAncestorWithLastFrame(nullptr),
1005 0 : mMissingFonts(aPresContext->MissingFontRecorder()),
1006 0 : mBidiEnabled(aPresContext->BidiEnabled()),
1007 : mSkipIncompleteTextRuns(false),
1008 : mWhichTextRun(aWhichTextRun),
1009 : mNextRunContextInfo(nsTextFrameUtils::INCOMING_NONE),
1010 0 : mCurrentRunContextInfo(nsTextFrameUtils::INCOMING_NONE) {
1011 0 : ResetRunInfo();
1012 0 : }
1013 0 : ~BuildTextRunsScanner() {
1014 0 : NS_ASSERTION(mBreakSinks.IsEmpty(), "Should have been cleared");
1015 0 : NS_ASSERTION(mLineBreakBeforeFrames.IsEmpty(), "Should have been cleared");
1016 0 : NS_ASSERTION(mMappedFlows.IsEmpty(), "Should have been cleared");
1017 0 : }
1018 :
1019 : void SetAtStartOfLine() {
1020 0 : mStartOfLine = true;
1021 0 : mCanStopOnThisLine = false;
1022 : }
1023 : void SetSkipIncompleteTextRuns(bool aSkip) {
1024 0 : mSkipIncompleteTextRuns = aSkip;
1025 : }
1026 : void SetCommonAncestorWithLastFrame(nsIFrame* aFrame) {
1027 0 : mCommonAncestorWithLastFrame = aFrame;
1028 : }
1029 : bool CanStopOnThisLine() {
1030 : return mCanStopOnThisLine;
1031 : }
1032 : nsIFrame* GetCommonAncestorWithLastFrame() {
1033 : return mCommonAncestorWithLastFrame;
1034 : }
1035 0 : void LiftCommonAncestorWithLastFrameToParent(nsIFrame* aFrame) {
1036 0 : if (mCommonAncestorWithLastFrame &&
1037 0 : mCommonAncestorWithLastFrame->GetParent() == aFrame) {
1038 0 : mCommonAncestorWithLastFrame = aFrame;
1039 : }
1040 0 : }
1041 : void ScanFrame(nsIFrame* aFrame);
1042 : bool IsTextRunValidForMappedFlows(const gfxTextRun* aTextRun);
1043 : void FlushFrames(bool aFlushLineBreaks, bool aSuppressTrailingBreak);
1044 : void FlushLineBreaks(gfxTextRun* aTrailingTextRun);
1045 0 : void ResetRunInfo() {
1046 0 : mLastFrame = nullptr;
1047 0 : mMappedFlows.Clear();
1048 0 : mLineBreakBeforeFrames.Clear();
1049 0 : mMaxTextLength = 0;
1050 0 : mDoubleByteText = false;
1051 0 : }
1052 : void AccumulateRunInfo(nsTextFrame* aFrame);
1053 : /**
1054 : * @return null to indicate either textrun construction failed or
1055 : * we constructed just a partial textrun to set up linebreaker and other
1056 : * state for following textruns.
1057 : */
1058 : already_AddRefed<gfxTextRun> BuildTextRunForFrames(void* aTextBuffer);
1059 : bool SetupLineBreakerContext(gfxTextRun *aTextRun);
1060 : void AssignTextRun(gfxTextRun* aTextRun, float aInflation);
1061 : nsTextFrame* GetNextBreakBeforeFrame(uint32_t* aIndex);
1062 : void SetupBreakSinksForTextRun(gfxTextRun* aTextRun, const void* aTextPtr);
1063 : void SetupTextEmphasisForTextRun(gfxTextRun* aTextRun, const void* aTextPtr);
1064 : struct FindBoundaryState {
1065 : nsIFrame* mStopAtFrame;
1066 : nsTextFrame* mFirstTextFrame;
1067 : nsTextFrame* mLastTextFrame;
1068 : bool mSeenTextRunBoundaryOnLaterLine;
1069 : bool mSeenTextRunBoundaryOnThisLine;
1070 : bool mSeenSpaceForLineBreakingOnThisLine;
1071 : nsTArray<char16_t>& mBuffer;
1072 : };
1073 : enum FindBoundaryResult {
1074 : FB_CONTINUE,
1075 : FB_STOPPED_AT_STOP_FRAME,
1076 : FB_FOUND_VALID_TEXTRUN_BOUNDARY
1077 : };
1078 : FindBoundaryResult FindBoundaries(nsIFrame* aFrame, FindBoundaryState* aState);
1079 :
1080 : bool ContinueTextRunAcrossFrames(nsTextFrame* aFrame1, nsTextFrame* aFrame2);
1081 :
1082 : // Like TextRunMappedFlow but with some differences. mStartFrame to mEndFrame
1083 : // (exclusive) are a sequence of in-flow frames (if mEndFrame is null, then
1084 : // continuations starting from mStartFrame are a sequence of in-flow frames).
1085 : struct MappedFlow {
1086 : nsTextFrame* mStartFrame;
1087 : nsTextFrame* mEndFrame;
1088 : // When we consider breaking between elements, the nearest common
1089 : // ancestor of the elements containing the characters is the one whose
1090 : // CSS 'white-space' property governs. So this records the nearest common
1091 : // ancestor of mStartFrame and the previous text frame, or null if there
1092 : // was no previous text frame on this line.
1093 : nsIFrame* mAncestorControllingInitialBreak;
1094 :
1095 0 : int32_t GetContentEnd() {
1096 0 : return mEndFrame ? mEndFrame->GetContentOffset()
1097 0 : : mStartFrame->GetContent()->GetText()->GetLength();
1098 : }
1099 : };
1100 :
1101 0 : class BreakSink final : public nsILineBreakSink {
1102 : public:
1103 : BreakSink(gfxTextRun* aTextRun, DrawTarget* aDrawTarget,
1104 : uint32_t aOffsetIntoTextRun)
1105 0 : : mTextRun(aTextRun)
1106 : , mDrawTarget(aDrawTarget)
1107 0 : , mOffsetIntoTextRun(aOffsetIntoTextRun)
1108 : {}
1109 :
1110 0 : virtual void SetBreaks(uint32_t aOffset, uint32_t aLength,
1111 : uint8_t* aBreakBefore) override {
1112 : gfxTextRun::Range range(aOffset + mOffsetIntoTextRun,
1113 0 : aOffset + mOffsetIntoTextRun + aLength);
1114 0 : if (mTextRun->SetPotentialLineBreaks(range, aBreakBefore)) {
1115 : // Be conservative and assume that some breaks have been set
1116 0 : mTextRun->ClearFlagBits(nsTextFrameUtils::Flags::TEXT_NO_BREAKS);
1117 : }
1118 0 : }
1119 :
1120 0 : virtual void SetCapitalization(uint32_t aOffset, uint32_t aLength,
1121 : bool* aCapitalize) override {
1122 0 : MOZ_ASSERT(mTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_IS_TRANSFORMED,
1123 : "Text run should be transformed!");
1124 0 : if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_IS_TRANSFORMED) {
1125 : nsTransformedTextRun* transformedTextRun =
1126 0 : static_cast<nsTransformedTextRun*>(mTextRun.get());
1127 0 : transformedTextRun->SetCapitalization(aOffset + mOffsetIntoTextRun, aLength,
1128 0 : aCapitalize);
1129 : }
1130 0 : }
1131 :
1132 0 : void Finish(gfxMissingFontRecorder* aMFR) {
1133 0 : MOZ_ASSERT(!(mTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_UNUSED_FLAGS),
1134 : "Flag set that should never be set! (memory safety error?)");
1135 0 : if (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_IS_TRANSFORMED) {
1136 : nsTransformedTextRun* transformedTextRun =
1137 0 : static_cast<nsTransformedTextRun*>(mTextRun.get());
1138 0 : transformedTextRun->FinishSettingProperties(mDrawTarget, aMFR);
1139 : }
1140 : // The way nsTransformedTextRun is implemented, its glyph runs aren't
1141 : // available until after nsTransformedTextRun::FinishSettingProperties()
1142 : // is called. So that's why we defer checking for animated glyphs to here.
1143 0 : CreateObserversForAnimatedGlyphs(mTextRun);
1144 0 : }
1145 :
1146 : RefPtr<gfxTextRun> mTextRun;
1147 : DrawTarget* mDrawTarget;
1148 : uint32_t mOffsetIntoTextRun;
1149 : };
1150 :
1151 : private:
1152 : AutoTArray<MappedFlow,10> mMappedFlows;
1153 : AutoTArray<nsTextFrame*,50> mLineBreakBeforeFrames;
1154 : AutoTArray<UniquePtr<BreakSink>,10> mBreakSinks;
1155 : nsLineBreaker mLineBreaker;
1156 : RefPtr<gfxTextRun> mCurrentFramesAllSameTextRun;
1157 : DrawTarget* mDrawTarget;
1158 : nsIFrame* mLineContainer;
1159 : nsTextFrame* mLastFrame;
1160 : // The common ancestor of the current frame and the previous leaf frame
1161 : // on the line, or null if there was no previous leaf frame.
1162 : nsIFrame* mCommonAncestorWithLastFrame;
1163 : gfxMissingFontRecorder* mMissingFonts;
1164 : // mMaxTextLength is an upper bound on the size of the text in all mapped frames
1165 : // The value UINT32_MAX represents overflow; text will be discarded
1166 : uint32_t mMaxTextLength;
1167 : bool mDoubleByteText;
1168 : bool mBidiEnabled;
1169 : bool mStartOfLine;
1170 : bool mSkipIncompleteTextRuns;
1171 : bool mCanStopOnThisLine;
1172 : nsTextFrame::TextRunType mWhichTextRun;
1173 : uint8_t mNextRunContextInfo;
1174 : uint8_t mCurrentRunContextInfo;
1175 : };
1176 :
1177 : static nsIFrame*
1178 0 : FindLineContainer(nsIFrame* aFrame)
1179 : {
1180 0 : while (aFrame && (aFrame->IsFrameOfType(nsIFrame::eLineParticipant) ||
1181 0 : aFrame->CanContinueTextRun())) {
1182 0 : aFrame = aFrame->GetParent();
1183 : }
1184 0 : return aFrame;
1185 : }
1186 :
1187 : static bool
1188 0 : IsLineBreakingWhiteSpace(char16_t aChar)
1189 : {
1190 : // 0x0A (\n) is not handled as white-space by the line breaker, since
1191 : // we break before it, if it isn't transformed to a normal space.
1192 : // (If we treat it as normal white-space then we'd only break after it.)
1193 : // However, it does induce a line break or is converted to a regular
1194 : // space, and either way it can be used to bound the region of text
1195 : // that needs to be analyzed for line breaking.
1196 0 : return nsLineBreaker::IsSpace(aChar) || aChar == 0x0A;
1197 : }
1198 :
1199 : static bool
1200 0 : TextContainsLineBreakerWhiteSpace(const void* aText, uint32_t aLength,
1201 : bool aIsDoubleByte)
1202 : {
1203 0 : if (aIsDoubleByte) {
1204 : const char16_t* chars = static_cast<const char16_t*>(aText);
1205 0 : for (uint32_t i = 0; i < aLength; ++i) {
1206 0 : if (IsLineBreakingWhiteSpace(chars[i]))
1207 : return true;
1208 : }
1209 : return false;
1210 : } else {
1211 : const uint8_t* chars = static_cast<const uint8_t*>(aText);
1212 0 : for (uint32_t i = 0; i < aLength; ++i) {
1213 0 : if (IsLineBreakingWhiteSpace(chars[i]))
1214 : return true;
1215 : }
1216 : return false;
1217 : }
1218 : }
1219 :
1220 : static_assert(uint8_t(mozilla::StyleWhiteSpace::Normal) == 0, "Convention: StyleWhiteSpace::Normal should be 0");
1221 : static_assert(uint8_t(mozilla::StyleWhiteSpace::Pre) == 1, "Convention: StyleWhiteSpace::Pre should be 1");
1222 : static_assert(uint8_t(mozilla::StyleWhiteSpace::Nowrap) == 2, "Convention: StyleWhiteSpace::NoWrap should be 2");
1223 : static_assert(uint8_t(mozilla::StyleWhiteSpace::PreWrap) == 3, "Convention: StyleWhiteSpace::PreWrap should be 3");
1224 : static_assert(uint8_t(mozilla::StyleWhiteSpace::PreLine) == 4, "Convention: StyleWhiteSpace::PreLine should be 4");
1225 : static_assert(uint8_t(mozilla::StyleWhiteSpace::PreSpace) == 5, "Convention: StyleWhiteSpace::PreSpace should be 5");
1226 :
1227 : static nsTextFrameUtils::CompressionMode
1228 0 : GetCSSWhitespaceToCompressionMode(nsTextFrame* aFrame,
1229 : const nsStyleText* aStyleText)
1230 : {
1231 : static const nsTextFrameUtils::CompressionMode sModes[] =
1232 : {
1233 : nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE, // normal
1234 : nsTextFrameUtils::COMPRESS_NONE, // pre
1235 : nsTextFrameUtils::COMPRESS_WHITESPACE_NEWLINE, // nowrap
1236 : nsTextFrameUtils::COMPRESS_NONE, // pre-wrap
1237 : nsTextFrameUtils::COMPRESS_WHITESPACE, // pre-line
1238 : nsTextFrameUtils::COMPRESS_NONE_TRANSFORM_TO_SPACE // -moz-pre-space
1239 : };
1240 :
1241 0 : auto compression = sModes[uint8_t(aStyleText->mWhiteSpace)];
1242 0 : if (compression == nsTextFrameUtils::COMPRESS_NONE &&
1243 0 : !aStyleText->NewlineIsSignificant(aFrame)) {
1244 : // If newline is set to be preserved, but then suppressed,
1245 : // transform newline to space.
1246 0 : compression = nsTextFrameUtils::COMPRESS_NONE_TRANSFORM_TO_SPACE;
1247 : }
1248 0 : return compression;
1249 : }
1250 :
1251 : struct FrameTextTraversal
1252 : {
1253 0 : FrameTextTraversal()
1254 0 : : mFrameToScan(nullptr)
1255 : , mOverflowFrameToScan(nullptr)
1256 : , mScanSiblings(false)
1257 : , mLineBreakerCanCrossFrameBoundary(false)
1258 0 : , mTextRunCanCrossFrameBoundary(false)
1259 0 : {}
1260 :
1261 : // These fields identify which frames should be recursively scanned
1262 : // The first normal frame to scan (or null, if no such frame should be scanned)
1263 : nsIFrame* mFrameToScan;
1264 : // The first overflow frame to scan (or null, if no such frame should be scanned)
1265 : nsIFrame* mOverflowFrameToScan;
1266 : // Whether to scan the siblings of mFrameToDescendInto/mOverflowFrameToDescendInto
1267 : bool mScanSiblings;
1268 :
1269 : // These identify the boundaries of the context required for
1270 : // line breaking or textrun construction
1271 : bool mLineBreakerCanCrossFrameBoundary;
1272 : bool mTextRunCanCrossFrameBoundary;
1273 :
1274 0 : nsIFrame* NextFrameToScan() {
1275 : nsIFrame* f;
1276 0 : if (mFrameToScan) {
1277 0 : f = mFrameToScan;
1278 0 : mFrameToScan = mScanSiblings ? f->GetNextSibling() : nullptr;
1279 0 : } else if (mOverflowFrameToScan) {
1280 0 : f = mOverflowFrameToScan;
1281 0 : mOverflowFrameToScan = mScanSiblings ? f->GetNextSibling() : nullptr;
1282 : } else {
1283 : f = nullptr;
1284 : }
1285 0 : return f;
1286 : }
1287 : };
1288 :
1289 : static FrameTextTraversal
1290 0 : CanTextCrossFrameBoundary(nsIFrame* aFrame)
1291 : {
1292 0 : FrameTextTraversal result;
1293 :
1294 0 : bool continuesTextRun = aFrame->CanContinueTextRun();
1295 0 : if (aFrame->IsPlaceholderFrame()) {
1296 : // placeholders are "invisible", so a text run should be able to span
1297 : // across one. But don't descend into the out-of-flow.
1298 0 : result.mLineBreakerCanCrossFrameBoundary = true;
1299 0 : if (continuesTextRun) {
1300 : // ... Except for first-letter floats, which are really in-flow
1301 : // from the point of view of capitalization etc, so we'd better
1302 : // descend into them. But we actually need to break the textrun for
1303 : // first-letter floats since things look bad if, say, we try to make a
1304 : // ligature across the float boundary.
1305 0 : result.mFrameToScan =
1306 0 : (static_cast<nsPlaceholderFrame*>(aFrame))->GetOutOfFlowFrame();
1307 : } else {
1308 0 : result.mTextRunCanCrossFrameBoundary = true;
1309 : }
1310 : } else {
1311 0 : if (continuesTextRun) {
1312 0 : result.mFrameToScan = aFrame->PrincipalChildList().FirstChild();
1313 0 : result.mOverflowFrameToScan =
1314 0 : aFrame->GetChildList(nsIFrame::kOverflowList).FirstChild();
1315 0 : NS_WARNING_ASSERTION(
1316 : !result.mOverflowFrameToScan,
1317 : "Scanning overflow inline frames is something we should avoid");
1318 0 : result.mScanSiblings = true;
1319 0 : result.mTextRunCanCrossFrameBoundary = true;
1320 0 : result.mLineBreakerCanCrossFrameBoundary = true;
1321 : } else {
1322 0 : MOZ_ASSERT(!aFrame->IsRubyTextContainerFrame(),
1323 : "Shouldn't call this method for ruby text container");
1324 : }
1325 : }
1326 0 : return result;
1327 : }
1328 :
1329 : BuildTextRunsScanner::FindBoundaryResult
1330 0 : BuildTextRunsScanner::FindBoundaries(nsIFrame* aFrame, FindBoundaryState* aState)
1331 : {
1332 0 : LayoutFrameType frameType = aFrame->Type();
1333 0 : if (frameType == LayoutFrameType::RubyTextContainer) {
1334 : // Don't stop a text run for ruby text container. We want ruby text
1335 : // containers to be skipped, but continue the text run across them.
1336 : return FB_CONTINUE;
1337 : }
1338 :
1339 : nsTextFrame* textFrame = frameType == LayoutFrameType::Text
1340 0 : ? static_cast<nsTextFrame*>(aFrame)
1341 0 : : nullptr;
1342 0 : if (textFrame) {
1343 0 : if (aState->mLastTextFrame &&
1344 0 : textFrame != aState->mLastTextFrame->GetNextInFlow() &&
1345 0 : !ContinueTextRunAcrossFrames(aState->mLastTextFrame, textFrame)) {
1346 0 : aState->mSeenTextRunBoundaryOnThisLine = true;
1347 0 : if (aState->mSeenSpaceForLineBreakingOnThisLine)
1348 : return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
1349 : }
1350 0 : if (!aState->mFirstTextFrame) {
1351 0 : aState->mFirstTextFrame = textFrame;
1352 : }
1353 0 : aState->mLastTextFrame = textFrame;
1354 : }
1355 :
1356 0 : if (aFrame == aState->mStopAtFrame)
1357 : return FB_STOPPED_AT_STOP_FRAME;
1358 :
1359 0 : if (textFrame) {
1360 0 : if (aState->mSeenSpaceForLineBreakingOnThisLine) {
1361 : return FB_CONTINUE;
1362 : }
1363 0 : const nsTextFragment* frag = textFrame->GetContent()->GetText();
1364 0 : uint32_t start = textFrame->GetContentOffset();
1365 0 : uint32_t length = textFrame->GetContentLength();
1366 : const void* text;
1367 0 : if (frag->Is2b()) {
1368 : // It is possible that we may end up removing all whitespace in
1369 : // a piece of text because of The White Space Processing Rules,
1370 : // so we need to transform it before we can check existence of
1371 : // such whitespaces.
1372 0 : aState->mBuffer.EnsureLengthAtLeast(length);
1373 : nsTextFrameUtils::CompressionMode compression =
1374 0 : GetCSSWhitespaceToCompressionMode(textFrame, textFrame->StyleText());
1375 0 : uint8_t incomingFlags = 0;
1376 0 : gfxSkipChars skipChars;
1377 : nsTextFrameUtils::Flags analysisFlags;
1378 0 : char16_t* bufStart = aState->mBuffer.Elements();
1379 0 : char16_t* bufEnd = nsTextFrameUtils::TransformText(
1380 0 : frag->Get2b() + start, length, bufStart, compression,
1381 0 : &incomingFlags, &skipChars, &analysisFlags);
1382 0 : text = bufStart;
1383 0 : length = bufEnd - bufStart;
1384 : } else {
1385 : // If the text only contains ASCII characters, it is currently
1386 : // impossible that TransformText would remove all whitespaces,
1387 : // and thus the check below should return the same result for
1388 : // transformed text and original text. So we don't need to try
1389 : // transforming it here.
1390 0 : text = static_cast<const void*>(frag->Get1b() + start);
1391 : }
1392 0 : if (TextContainsLineBreakerWhiteSpace(text, length, frag->Is2b())) {
1393 0 : aState->mSeenSpaceForLineBreakingOnThisLine = true;
1394 0 : if (aState->mSeenTextRunBoundaryOnLaterLine) {
1395 : return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
1396 : }
1397 : }
1398 : return FB_CONTINUE;
1399 : }
1400 :
1401 0 : FrameTextTraversal traversal = CanTextCrossFrameBoundary(aFrame);
1402 0 : if (!traversal.mTextRunCanCrossFrameBoundary) {
1403 0 : aState->mSeenTextRunBoundaryOnThisLine = true;
1404 0 : if (aState->mSeenSpaceForLineBreakingOnThisLine)
1405 : return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
1406 : }
1407 :
1408 0 : for (nsIFrame* f = traversal.NextFrameToScan(); f;
1409 : f = traversal.NextFrameToScan()) {
1410 0 : FindBoundaryResult result = FindBoundaries(f, aState);
1411 0 : if (result != FB_CONTINUE)
1412 : return result;
1413 : }
1414 :
1415 0 : if (!traversal.mTextRunCanCrossFrameBoundary) {
1416 0 : aState->mSeenTextRunBoundaryOnThisLine = true;
1417 0 : if (aState->mSeenSpaceForLineBreakingOnThisLine)
1418 : return FB_FOUND_VALID_TEXTRUN_BOUNDARY;
1419 : }
1420 :
1421 : return FB_CONTINUE;
1422 : }
1423 :
1424 : // build text runs for the 200 lines following aForFrame, and stop after that
1425 : // when we get a chance.
1426 : #define NUM_LINES_TO_BUILD_TEXT_RUNS 200
1427 :
1428 : /**
1429 : * General routine for building text runs. This is hairy because of the need
1430 : * to build text runs that span content nodes.
1431 : *
1432 : * @param aContext The gfxContext we're using to construct this text run.
1433 : * @param aForFrame The nsTextFrame for which we're building this text run.
1434 : * @param aLineContainer the line container containing aForFrame; if null,
1435 : * we'll walk the ancestors to find it. It's required to be non-null
1436 : * when aForFrameLine is non-null.
1437 : * @param aForFrameLine the line containing aForFrame; if null, we'll figure
1438 : * out the line (slowly)
1439 : * @param aWhichTextRun The type of text run we want to build. If font inflation
1440 : * is enabled, this will be eInflated, otherwise it's eNotInflated.
1441 : */
1442 : static void
1443 0 : BuildTextRuns(DrawTarget* aDrawTarget, nsTextFrame* aForFrame,
1444 : nsIFrame* aLineContainer,
1445 : const nsLineList::iterator* aForFrameLine,
1446 : nsTextFrame::TextRunType aWhichTextRun)
1447 : {
1448 0 : MOZ_ASSERT(aForFrame, "for no frame?");
1449 0 : NS_ASSERTION(!aForFrameLine || aLineContainer,
1450 : "line but no line container");
1451 :
1452 0 : nsIFrame* lineContainerChild = aForFrame;
1453 0 : if (!aLineContainer) {
1454 0 : if (aForFrame->IsFloatingFirstLetterChild()) {
1455 0 : lineContainerChild = aForFrame->GetParent()->GetPlaceholderFrame();
1456 : }
1457 0 : aLineContainer = FindLineContainer(lineContainerChild);
1458 : } else {
1459 0 : NS_ASSERTION((aLineContainer == FindLineContainer(aForFrame) ||
1460 : (aLineContainer->IsLetterFrame() &&
1461 : aLineContainer->IsFloating())),
1462 : "Wrong line container hint");
1463 : }
1464 :
1465 0 : if (aForFrame->HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) {
1466 0 : aLineContainer->AddStateBits(TEXT_IS_IN_TOKEN_MATHML);
1467 0 : if (aForFrame->HasAnyStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI)) {
1468 : aLineContainer->AddStateBits(NS_FRAME_IS_IN_SINGLE_CHAR_MI);
1469 : }
1470 : }
1471 0 : if (aForFrame->HasAnyStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT)) {
1472 : aLineContainer->AddStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT);
1473 : }
1474 :
1475 0 : nsPresContext* presContext = aLineContainer->PresContext();
1476 : BuildTextRunsScanner scanner(presContext, aDrawTarget,
1477 0 : aLineContainer, aWhichTextRun);
1478 :
1479 0 : nsBlockFrame* block = nsLayoutUtils::GetAsBlock(aLineContainer);
1480 :
1481 0 : if (!block) {
1482 0 : nsIFrame* textRunContainer = aLineContainer;
1483 0 : if (aLineContainer->IsRubyTextContainerFrame()) {
1484 : textRunContainer = aForFrame;
1485 0 : while (textRunContainer && !textRunContainer->IsRubyTextFrame()) {
1486 0 : textRunContainer = textRunContainer->GetParent();
1487 : }
1488 0 : MOZ_ASSERT(textRunContainer &&
1489 : textRunContainer->GetParent() == aLineContainer);
1490 : } else {
1491 0 : NS_ASSERTION(
1492 : !aLineContainer->GetPrevInFlow() && !aLineContainer->GetNextInFlow(),
1493 : "Breakable non-block line containers other than "
1494 : "ruby text container is not supported");
1495 : }
1496 : // Just loop through all the children of the linecontainer ... it's really
1497 : // just one line
1498 0 : scanner.SetAtStartOfLine();
1499 0 : scanner.SetCommonAncestorWithLastFrame(nullptr);
1500 0 : for (nsIFrame* child : textRunContainer->PrincipalChildList()) {
1501 0 : scanner.ScanFrame(child);
1502 : }
1503 : // Set mStartOfLine so FlushFrames knows its textrun ends a line
1504 0 : scanner.SetAtStartOfLine();
1505 0 : scanner.FlushFrames(true, false);
1506 0 : return;
1507 : }
1508 :
1509 : // Find the line containing 'lineContainerChild'.
1510 :
1511 0 : bool isValid = true;
1512 0 : nsBlockInFlowLineIterator backIterator(block, &isValid);
1513 0 : if (aForFrameLine) {
1514 0 : backIterator = nsBlockInFlowLineIterator(block, *aForFrameLine);
1515 : } else {
1516 0 : backIterator = nsBlockInFlowLineIterator(block, lineContainerChild, &isValid);
1517 0 : NS_ASSERTION(isValid, "aForFrame not found in block, someone lied to us");
1518 0 : NS_ASSERTION(backIterator.GetContainer() == block,
1519 : "Someone lied to us about the block");
1520 : }
1521 0 : nsBlockFrame::LineIterator startLine = backIterator.GetLine();
1522 :
1523 : // Find a line where we can start building text runs. We choose the last line
1524 : // where:
1525 : // -- there is a textrun boundary between the start of the line and the
1526 : // start of aForFrame
1527 : // -- there is a space between the start of the line and the textrun boundary
1528 : // (this is so we can be sure the line breaks will be set properly
1529 : // on the textruns we construct).
1530 : // The possibly-partial text runs up to and including the first space
1531 : // are not reconstructed. We construct partial text runs for that text ---
1532 : // for the sake of simplifying the code and feeding the linebreaker ---
1533 : // but we discard them instead of assigning them to frames.
1534 : // This is a little awkward because we traverse lines in the reverse direction
1535 : // but we traverse the frames in each line in the forward direction.
1536 0 : nsBlockInFlowLineIterator forwardIterator = backIterator;
1537 0 : nsIFrame* stopAtFrame = lineContainerChild;
1538 0 : nsTextFrame* nextLineFirstTextFrame = nullptr;
1539 0 : AutoTArray<char16_t, BIG_TEXT_NODE_SIZE> buffer;
1540 0 : bool seenTextRunBoundaryOnLaterLine = false;
1541 0 : bool mayBeginInTextRun = true;
1542 : while (true) {
1543 0 : forwardIterator = backIterator;
1544 0 : nsBlockFrame::LineIterator line = backIterator.GetLine();
1545 0 : if (!backIterator.Prev() || backIterator.GetLine()->IsBlock()) {
1546 : mayBeginInTextRun = false;
1547 0 : break;
1548 : }
1549 :
1550 : BuildTextRunsScanner::FindBoundaryState state = { stopAtFrame, nullptr, nullptr,
1551 0 : bool(seenTextRunBoundaryOnLaterLine), false, false, buffer };
1552 0 : nsIFrame* child = line->mFirstChild;
1553 0 : bool foundBoundary = false;
1554 0 : for (int32_t i = line->GetChildCount() - 1; i >= 0; --i) {
1555 : BuildTextRunsScanner::FindBoundaryResult result =
1556 0 : scanner.FindBoundaries(child, &state);
1557 0 : if (result == BuildTextRunsScanner::FB_FOUND_VALID_TEXTRUN_BOUNDARY) {
1558 : foundBoundary = true;
1559 : break;
1560 0 : } else if (result == BuildTextRunsScanner::FB_STOPPED_AT_STOP_FRAME) {
1561 : break;
1562 : }
1563 0 : child = child->GetNextSibling();
1564 : }
1565 0 : if (foundBoundary)
1566 : break;
1567 0 : if (!stopAtFrame && state.mLastTextFrame && nextLineFirstTextFrame &&
1568 0 : !scanner.ContinueTextRunAcrossFrames(state.mLastTextFrame, nextLineFirstTextFrame)) {
1569 : // Found a usable textrun boundary at the end of the line
1570 0 : if (state.mSeenSpaceForLineBreakingOnThisLine)
1571 : break;
1572 : seenTextRunBoundaryOnLaterLine = true;
1573 0 : } else if (state.mSeenTextRunBoundaryOnThisLine) {
1574 0 : seenTextRunBoundaryOnLaterLine = true;
1575 : }
1576 0 : stopAtFrame = nullptr;
1577 0 : if (state.mFirstTextFrame) {
1578 0 : nextLineFirstTextFrame = state.mFirstTextFrame;
1579 : }
1580 0 : }
1581 0 : scanner.SetSkipIncompleteTextRuns(mayBeginInTextRun);
1582 :
1583 : // Now iterate over all text frames starting from the current line. First-in-flow
1584 : // text frames will be accumulated into textRunFrames as we go. When a
1585 : // text run boundary is required we flush textRunFrames ((re)building their
1586 : // gfxTextRuns as necessary).
1587 0 : bool seenStartLine = false;
1588 0 : uint32_t linesAfterStartLine = 0;
1589 0 : do {
1590 0 : nsBlockFrame::LineIterator line = forwardIterator.GetLine();
1591 0 : if (line->IsBlock())
1592 : break;
1593 0 : line->SetInvalidateTextRuns(false);
1594 0 : scanner.SetAtStartOfLine();
1595 0 : scanner.SetCommonAncestorWithLastFrame(nullptr);
1596 0 : nsIFrame* child = line->mFirstChild;
1597 0 : for (int32_t i = line->GetChildCount() - 1; i >= 0; --i) {
1598 0 : scanner.ScanFrame(child);
1599 0 : child = child->GetNextSibling();
1600 : }
1601 0 : if (line.get() == startLine.get()) {
1602 0 : seenStartLine = true;
1603 : }
1604 0 : if (seenStartLine) {
1605 0 : ++linesAfterStartLine;
1606 0 : if (linesAfterStartLine >= NUM_LINES_TO_BUILD_TEXT_RUNS && scanner.CanStopOnThisLine()) {
1607 : // Don't flush frames; we may be in the middle of a textrun
1608 : // that we can't end here. That's OK, we just won't build it.
1609 : // Note that we must already have finished the textrun for aForFrame,
1610 : // because we've seen the end of a textrun in a line after the line
1611 : // containing aForFrame.
1612 0 : scanner.FlushLineBreaks(nullptr);
1613 : // This flushes out mMappedFlows and mLineBreakBeforeFrames, which
1614 : // silences assertions in the scanner destructor.
1615 0 : scanner.ResetRunInfo();
1616 0 : return;
1617 : }
1618 : }
1619 : } while (forwardIterator.Next());
1620 :
1621 : // Set mStartOfLine so FlushFrames knows its textrun ends a line
1622 0 : scanner.SetAtStartOfLine();
1623 0 : scanner.FlushFrames(true, false);
1624 : }
1625 :
1626 : static char16_t*
1627 0 : ExpandBuffer(char16_t* aDest, uint8_t* aSrc, uint32_t aCount)
1628 : {
1629 0 : while (aCount) {
1630 0 : *aDest = *aSrc;
1631 0 : ++aDest;
1632 0 : ++aSrc;
1633 0 : --aCount;
1634 : }
1635 0 : return aDest;
1636 : }
1637 :
1638 : bool
1639 0 : BuildTextRunsScanner::IsTextRunValidForMappedFlows(const gfxTextRun* aTextRun)
1640 : {
1641 0 : if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_IS_SIMPLE_FLOW) {
1642 0 : return mMappedFlows.Length() == 1 &&
1643 0 : mMappedFlows[0].mStartFrame == GetFrameForSimpleFlow(aTextRun) &&
1644 0 : mMappedFlows[0].mEndFrame == nullptr;
1645 : }
1646 :
1647 0 : auto userData = static_cast<TextRunUserData*>(aTextRun->GetUserData());
1648 0 : TextRunMappedFlow* userMappedFlows = GetMappedFlows(aTextRun);
1649 0 : if (userData->mMappedFlowCount != mMappedFlows.Length())
1650 : return false;
1651 0 : for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
1652 0 : if (userMappedFlows[i].mStartFrame != mMappedFlows[i].mStartFrame ||
1653 0 : int32_t(userMappedFlows[i].mContentLength) !=
1654 0 : mMappedFlows[i].GetContentEnd() - mMappedFlows[i].mStartFrame->GetContentOffset())
1655 : return false;
1656 : }
1657 : return true;
1658 : }
1659 :
1660 : /**
1661 : * This gets called when we need to make a text run for the current list of
1662 : * frames.
1663 : */
1664 0 : void BuildTextRunsScanner::FlushFrames(bool aFlushLineBreaks, bool aSuppressTrailingBreak)
1665 : {
1666 0 : RefPtr<gfxTextRun> textRun;
1667 0 : if (!mMappedFlows.IsEmpty()) {
1668 0 : if (!mSkipIncompleteTextRuns && mCurrentFramesAllSameTextRun &&
1669 0 : !!(mCurrentFramesAllSameTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_INCOMING_WHITESPACE) ==
1670 0 : !!(mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) &&
1671 0 : !!(mCurrentFramesAllSameTextRun->GetFlags() & gfx::ShapedTextFlags::TEXT_INCOMING_ARABICCHAR) ==
1672 0 : !!(mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) &&
1673 0 : IsTextRunValidForMappedFlows(mCurrentFramesAllSameTextRun)) {
1674 : // Optimization: We do not need to (re)build the textrun.
1675 0 : textRun = mCurrentFramesAllSameTextRun;
1676 :
1677 : // Feed this run's text into the linebreaker to provide context.
1678 0 : if (!SetupLineBreakerContext(textRun)) {
1679 0 : return;
1680 : }
1681 :
1682 : // Update mNextRunContextInfo appropriately
1683 0 : mNextRunContextInfo = nsTextFrameUtils::INCOMING_NONE;
1684 0 : if (textRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_TRAILING_WHITESPACE) {
1685 0 : mNextRunContextInfo |= nsTextFrameUtils::INCOMING_WHITESPACE;
1686 : }
1687 0 : if (textRun->GetFlags() & gfx::ShapedTextFlags::TEXT_TRAILING_ARABICCHAR) {
1688 0 : mNextRunContextInfo |= nsTextFrameUtils::INCOMING_ARABICCHAR;
1689 : }
1690 : } else {
1691 0 : AutoTArray<uint8_t,BIG_TEXT_NODE_SIZE> buffer;
1692 0 : uint32_t bufferSize = mMaxTextLength*(mDoubleByteText ? 2 : 1);
1693 0 : if (bufferSize < mMaxTextLength || bufferSize == UINT32_MAX ||
1694 0 : !buffer.AppendElements(bufferSize, fallible)) {
1695 0 : return;
1696 : }
1697 0 : textRun = BuildTextRunForFrames(buffer.Elements());
1698 : }
1699 : }
1700 :
1701 0 : if (aFlushLineBreaks) {
1702 0 : FlushLineBreaks(aSuppressTrailingBreak ? nullptr : textRun.get());
1703 : }
1704 :
1705 0 : mCanStopOnThisLine = true;
1706 0 : ResetRunInfo();
1707 : }
1708 :
1709 0 : void BuildTextRunsScanner::FlushLineBreaks(gfxTextRun* aTrailingTextRun)
1710 : {
1711 : bool trailingLineBreak;
1712 0 : nsresult rv = mLineBreaker.Reset(&trailingLineBreak);
1713 : // textRun may be null for various reasons, including because we constructed
1714 : // a partial textrun just to get the linebreaker and other state set up
1715 : // to build the next textrun.
1716 0 : if (NS_SUCCEEDED(rv) && trailingLineBreak && aTrailingTextRun) {
1717 : aTrailingTextRun->SetFlagBits(nsTextFrameUtils::Flags::TEXT_HAS_TRAILING_BREAK);
1718 : }
1719 :
1720 0 : for (uint32_t i = 0; i < mBreakSinks.Length(); ++i) {
1721 : // TODO cause frames associated with the textrun to be reflowed, if they
1722 : // aren't being reflowed already!
1723 0 : mBreakSinks[i]->Finish(mMissingFonts);
1724 : }
1725 0 : mBreakSinks.Clear();
1726 0 : }
1727 :
1728 0 : void BuildTextRunsScanner::AccumulateRunInfo(nsTextFrame* aFrame)
1729 : {
1730 0 : if (mMaxTextLength != UINT32_MAX) {
1731 0 : NS_ASSERTION(mMaxTextLength < UINT32_MAX - aFrame->GetContentLength(), "integer overflow");
1732 0 : if (mMaxTextLength >= UINT32_MAX - aFrame->GetContentLength()) {
1733 0 : mMaxTextLength = UINT32_MAX;
1734 : } else {
1735 0 : mMaxTextLength += aFrame->GetContentLength();
1736 : }
1737 : }
1738 0 : mDoubleByteText |= aFrame->GetContent()->GetText()->Is2b();
1739 0 : mLastFrame = aFrame;
1740 0 : mCommonAncestorWithLastFrame = aFrame->GetParent();
1741 :
1742 0 : MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1];
1743 0 : NS_ASSERTION(mappedFlow->mStartFrame == aFrame ||
1744 : mappedFlow->GetContentEnd() == aFrame->GetContentOffset(),
1745 : "Overlapping or discontiguous frames => BAD");
1746 0 : mappedFlow->mEndFrame = aFrame->GetNextContinuation();
1747 0 : if (mCurrentFramesAllSameTextRun != aFrame->GetTextRun(mWhichTextRun)) {
1748 0 : mCurrentFramesAllSameTextRun = nullptr;
1749 : }
1750 :
1751 0 : if (mStartOfLine) {
1752 0 : mLineBreakBeforeFrames.AppendElement(aFrame);
1753 0 : mStartOfLine = false;
1754 : }
1755 0 : }
1756 :
1757 : static bool
1758 0 : HasTerminalNewline(const nsTextFrame* aFrame)
1759 : {
1760 0 : if (aFrame->GetContentLength() == 0)
1761 : return false;
1762 0 : const nsTextFragment* frag = aFrame->GetContent()->GetText();
1763 0 : return frag->CharAt(aFrame->GetContentEnd() - 1) == '\n';
1764 : }
1765 :
1766 : static gfxFont::Metrics
1767 0 : GetFirstFontMetrics(gfxFontGroup* aFontGroup, bool aVerticalMetrics)
1768 : {
1769 0 : if (!aFontGroup)
1770 0 : return gfxFont::Metrics();
1771 0 : gfxFont* font = aFontGroup->GetFirstValidFont();
1772 : return font->GetMetrics(aVerticalMetrics ? gfxFont::eVertical
1773 0 : : gfxFont::eHorizontal);
1774 : }
1775 :
1776 : static gfxFloat
1777 0 : GetSpaceWidthAppUnits(const gfxTextRun* aTextRun)
1778 : {
1779 : // Round the space width when converting to appunits the same way textruns
1780 : // do.
1781 : gfxFloat spaceWidthAppUnits =
1782 0 : NS_round(GetFirstFontMetrics(aTextRun->GetFontGroup(),
1783 0 : aTextRun->UseCenterBaseline()).spaceWidth *
1784 0 : aTextRun->GetAppUnitsPerDevUnit());
1785 :
1786 0 : return spaceWidthAppUnits;
1787 : }
1788 :
1789 : static nscoord
1790 0 : LetterSpacing(nsIFrame* aFrame, const nsStyleText* aStyleText = nullptr)
1791 : {
1792 0 : if (nsSVGUtils::IsInSVGTextSubtree(aFrame)) {
1793 : return 0;
1794 : }
1795 0 : if (!aStyleText) {
1796 0 : aStyleText = aFrame->StyleText();
1797 : }
1798 :
1799 0 : const nsStyleCoord& coord = aStyleText->mLetterSpacing;
1800 0 : if (eStyleUnit_Coord == coord.GetUnit()) {
1801 0 : return coord.GetCoordValue();
1802 : }
1803 : return 0;
1804 : }
1805 :
1806 : // This function converts non-coord values (e.g. percentages) to nscoord.
1807 : static nscoord
1808 0 : WordSpacing(nsIFrame* aFrame, const gfxTextRun* aTextRun,
1809 : const nsStyleText* aStyleText = nullptr)
1810 : {
1811 0 : if (nsSVGUtils::IsInSVGTextSubtree(aFrame)) {
1812 : return 0;
1813 : }
1814 0 : if (!aStyleText) {
1815 0 : aStyleText = aFrame->StyleText();
1816 : }
1817 :
1818 0 : const nsStyleCoord& coord = aStyleText->mWordSpacing;
1819 0 : if (coord.IsCoordPercentCalcUnit()) {
1820 0 : nscoord pctBasis = coord.HasPercent() ? GetSpaceWidthAppUnits(aTextRun) : 0;
1821 0 : return coord.ComputeCoordPercentCalc(pctBasis);
1822 : }
1823 : return 0;
1824 : }
1825 :
1826 : // Returns gfxTextRunFactory::TEXT_ENABLE_SPACING if non-standard
1827 : // letter-spacing or word-spacing is present.
1828 : static gfx::ShapedTextFlags
1829 0 : GetSpacingFlags(nsIFrame* aFrame, const nsStyleText* aStyleText = nullptr)
1830 : {
1831 0 : if (nsSVGUtils::IsInSVGTextSubtree(aFrame)) {
1832 : return gfx::ShapedTextFlags();
1833 : }
1834 :
1835 0 : const nsStyleText* styleText = aFrame->StyleText();
1836 0 : const nsStyleCoord& ls = styleText->mLetterSpacing;
1837 0 : const nsStyleCoord& ws = styleText->mWordSpacing;
1838 :
1839 : // It's possible to have a calc() value that computes to zero but for which
1840 : // IsDefinitelyZero() is false, in which case we'll return
1841 : // TEXT_ENABLE_SPACING unnecessarily. That's ok because such cases are likely
1842 : // to be rare, and avoiding TEXT_ENABLE_SPACING is just an optimization.
1843 : bool nonStandardSpacing =
1844 0 : (eStyleUnit_Coord == ls.GetUnit() && ls.GetCoordValue() != 0) ||
1845 0 : (eStyleUnit_Coord == ws.GetUnit() && ws.GetCoordValue() != 0) ||
1846 0 : (eStyleUnit_Percent == ws.GetUnit() && ws.GetPercentValue() != 0) ||
1847 0 : (eStyleUnit_Calc == ws.GetUnit() && !ws.GetCalcValue()->IsDefinitelyZero());
1848 :
1849 : return nonStandardSpacing
1850 0 : ? gfx::ShapedTextFlags::TEXT_ENABLE_SPACING
1851 : : gfx::ShapedTextFlags();
1852 : }
1853 :
1854 : bool
1855 0 : BuildTextRunsScanner::ContinueTextRunAcrossFrames(nsTextFrame* aFrame1, nsTextFrame* aFrame2)
1856 : {
1857 : // We don't need to check font size inflation, since
1858 : // |FindLineContainer| above (via |nsIFrame::CanContinueTextRun|)
1859 : // ensures that text runs never cross block boundaries. This means
1860 : // that the font size inflation on all text frames in the text run is
1861 : // already guaranteed to be the same as each other (and for the line
1862 : // container).
1863 0 : if (mBidiEnabled) {
1864 0 : FrameBidiData data1 = aFrame1->GetBidiData();
1865 0 : FrameBidiData data2 = aFrame2->GetBidiData();
1866 0 : if (data1.embeddingLevel != data2.embeddingLevel ||
1867 0 : data2.precedingControl != kBidiLevelNone) {
1868 0 : return false;
1869 : }
1870 : }
1871 :
1872 0 : ComputedStyle* sc1 = aFrame1->Style();
1873 0 : const nsStyleText* textStyle1 = sc1->StyleText();
1874 : // If the first frame ends in a preformatted newline, then we end the textrun
1875 : // here. This avoids creating giant textruns for an entire plain text file.
1876 : // Note that we create a single text frame for a preformatted text node,
1877 : // even if it has newlines in it, so typically we won't see trailing newlines
1878 : // until after reflow has broken up the frame into one (or more) frames per
1879 : // line. That's OK though.
1880 0 : if (textStyle1->NewlineIsSignificant(aFrame1) && HasTerminalNewline(aFrame1))
1881 : return false;
1882 :
1883 0 : if (aFrame1->GetContent() == aFrame2->GetContent() &&
1884 0 : aFrame1->GetNextInFlow() != aFrame2) {
1885 : // aFrame2 must be a non-fluid continuation of aFrame1. This can happen
1886 : // sometimes when the unicode-bidi property is used; the bidi resolver
1887 : // breaks text into different frames even though the text has the same
1888 : // direction. We can't allow these two frames to share the same textrun
1889 : // because that would violate our invariant that two flows in the same
1890 : // textrun have different content elements.
1891 : return false;
1892 : }
1893 :
1894 0 : ComputedStyle* sc2 = aFrame2->Style();
1895 0 : const nsStyleText* textStyle2 = sc2->StyleText();
1896 0 : if (sc1 == sc2)
1897 : return true;
1898 :
1899 0 : nsPresContext* pc = aFrame1->PresContext();
1900 0 : MOZ_ASSERT(pc == aFrame2->PresContext());
1901 :
1902 0 : const nsStyleFont* fontStyle1 = sc1->StyleFont();
1903 0 : const nsStyleFont* fontStyle2 = sc2->StyleFont();
1904 0 : nscoord letterSpacing1 = LetterSpacing(aFrame1);
1905 0 : nscoord letterSpacing2 = LetterSpacing(aFrame2);
1906 0 : return fontStyle1->mFont == fontStyle2->mFont &&
1907 0 : fontStyle1->mLanguage == fontStyle2->mLanguage &&
1908 0 : textStyle1->mTextTransform == textStyle2->mTextTransform &&
1909 0 : nsLayoutUtils::GetTextRunFlagsForStyle(sc1, pc, fontStyle1, textStyle1, letterSpacing1) ==
1910 0 : nsLayoutUtils::GetTextRunFlagsForStyle(sc2, pc, fontStyle2, textStyle2, letterSpacing2);
1911 : }
1912 :
1913 0 : void BuildTextRunsScanner::ScanFrame(nsIFrame* aFrame)
1914 : {
1915 0 : LayoutFrameType frameType = aFrame->Type();
1916 0 : if (frameType == LayoutFrameType::RubyTextContainer) {
1917 : // Don't include any ruby text container into the text run.
1918 0 : return;
1919 : }
1920 :
1921 : // First check if we can extend the current mapped frame block. This is common.
1922 0 : if (mMappedFlows.Length() > 0) {
1923 0 : MappedFlow* mappedFlow = &mMappedFlows[mMappedFlows.Length() - 1];
1924 0 : if (mappedFlow->mEndFrame == aFrame &&
1925 0 : (aFrame->GetStateBits() & NS_FRAME_IS_FLUID_CONTINUATION)) {
1926 0 : NS_ASSERTION(frameType == LayoutFrameType::Text,
1927 : "Flow-sibling of a text frame is not a text frame?");
1928 :
1929 : // Don't do this optimization if mLastFrame has a terminal newline...
1930 : // it's quite likely preformatted and we might want to end the textrun here.
1931 : // This is almost always true:
1932 0 : if (mLastFrame->Style() == aFrame->Style() &&
1933 0 : !HasTerminalNewline(mLastFrame)) {
1934 0 : AccumulateRunInfo(static_cast<nsTextFrame*>(aFrame));
1935 0 : return;
1936 : }
1937 : }
1938 : }
1939 :
1940 : // Now see if we can add a new set of frames to the current textrun
1941 0 : if (frameType == LayoutFrameType::Text) {
1942 0 : nsTextFrame* frame = static_cast<nsTextFrame*>(aFrame);
1943 :
1944 0 : if (mLastFrame) {
1945 0 : if (!ContinueTextRunAcrossFrames(mLastFrame, frame)) {
1946 0 : FlushFrames(false, false);
1947 : } else {
1948 0 : if (mLastFrame->GetContent() == frame->GetContent()) {
1949 0 : AccumulateRunInfo(frame);
1950 0 : return;
1951 : }
1952 : }
1953 : }
1954 :
1955 0 : MappedFlow* mappedFlow = mMappedFlows.AppendElement();
1956 0 : if (!mappedFlow)
1957 : return;
1958 :
1959 0 : mappedFlow->mStartFrame = frame;
1960 0 : mappedFlow->mAncestorControllingInitialBreak = mCommonAncestorWithLastFrame;
1961 :
1962 0 : AccumulateRunInfo(frame);
1963 0 : if (mMappedFlows.Length() == 1) {
1964 0 : mCurrentFramesAllSameTextRun = frame->GetTextRun(mWhichTextRun);
1965 0 : mCurrentRunContextInfo = mNextRunContextInfo;
1966 : }
1967 : return;
1968 : }
1969 :
1970 0 : FrameTextTraversal traversal = CanTextCrossFrameBoundary(aFrame);
1971 0 : bool isBR = frameType == LayoutFrameType::Br;
1972 0 : if (!traversal.mLineBreakerCanCrossFrameBoundary) {
1973 : // BR frames are special. We do not need or want to record a break opportunity
1974 : // before a BR frame.
1975 0 : FlushFrames(true, isBR);
1976 0 : mCommonAncestorWithLastFrame = aFrame;
1977 0 : mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE;
1978 0 : mStartOfLine = false;
1979 0 : } else if (!traversal.mTextRunCanCrossFrameBoundary) {
1980 0 : FlushFrames(false, false);
1981 : }
1982 :
1983 0 : for (nsIFrame* f = traversal.NextFrameToScan(); f;
1984 : f = traversal.NextFrameToScan()) {
1985 0 : ScanFrame(f);
1986 : }
1987 :
1988 0 : if (!traversal.mLineBreakerCanCrossFrameBoundary) {
1989 : // Really if we're a BR frame this is unnecessary since descendInto will be
1990 : // false. In fact this whole "if" statement should move into the descendInto.
1991 0 : FlushFrames(true, isBR);
1992 0 : mCommonAncestorWithLastFrame = aFrame;
1993 0 : mNextRunContextInfo &= ~nsTextFrameUtils::INCOMING_WHITESPACE;
1994 0 : } else if (!traversal.mTextRunCanCrossFrameBoundary) {
1995 0 : FlushFrames(false, false);
1996 : }
1997 :
1998 0 : LiftCommonAncestorWithLastFrameToParent(aFrame->GetParent());
1999 : }
2000 :
2001 : nsTextFrame*
2002 0 : BuildTextRunsScanner::GetNextBreakBeforeFrame(uint32_t* aIndex)
2003 : {
2004 0 : uint32_t index = *aIndex;
2005 0 : if (index >= mLineBreakBeforeFrames.Length())
2006 : return nullptr;
2007 0 : *aIndex = index + 1;
2008 0 : return static_cast<nsTextFrame*>(mLineBreakBeforeFrames.ElementAt(index));
2009 : }
2010 :
2011 : static gfxFontGroup*
2012 0 : GetFontGroupForFrame(const nsIFrame* aFrame, float aFontSizeInflation,
2013 : nsFontMetrics** aOutFontMetrics = nullptr)
2014 : {
2015 : RefPtr<nsFontMetrics> metrics =
2016 0 : nsLayoutUtils::GetFontMetricsForFrame(aFrame, aFontSizeInflation);
2017 0 : gfxFontGroup* fontGroup = metrics->GetThebesFontGroup();
2018 :
2019 : // Populate outparam before we return:
2020 0 : if (aOutFontMetrics) {
2021 0 : metrics.forget(aOutFontMetrics);
2022 : }
2023 : // XXX this is a bit bogus, we're releasing 'metrics' so the
2024 : // returned font-group might actually be torn down, although because
2025 : // of the way the device context caches font metrics, this seems to
2026 : // not actually happen. But we should fix this.
2027 0 : return fontGroup;
2028 : }
2029 :
2030 : static already_AddRefed<DrawTarget>
2031 0 : CreateReferenceDrawTarget(const nsTextFrame* aTextFrame)
2032 : {
2033 : RefPtr<gfxContext> ctx =
2034 0 : aTextFrame->PresShell()->CreateReferenceRenderingContext();
2035 0 : RefPtr<DrawTarget> dt = ctx->GetDrawTarget();
2036 0 : return dt.forget();
2037 : }
2038 :
2039 : static already_AddRefed<gfxTextRun>
2040 0 : GetHyphenTextRun(const gfxTextRun* aTextRun, DrawTarget* aDrawTarget,
2041 : nsTextFrame* aTextFrame)
2042 : {
2043 0 : RefPtr<DrawTarget> dt = aDrawTarget;
2044 0 : if (!dt) {
2045 0 : dt = CreateReferenceDrawTarget(aTextFrame);
2046 0 : if (!dt) {
2047 : return nullptr;
2048 : }
2049 : }
2050 :
2051 : return aTextRun->GetFontGroup()->
2052 0 : MakeHyphenTextRun(dt, aTextRun->GetAppUnitsPerDevUnit());
2053 : }
2054 :
2055 : already_AddRefed<gfxTextRun>
2056 0 : BuildTextRunsScanner::BuildTextRunForFrames(void* aTextBuffer)
2057 : {
2058 0 : gfxSkipChars skipChars;
2059 :
2060 0 : const void* textPtr = aTextBuffer;
2061 0 : bool anyTextTransformStyle = false;
2062 0 : bool anyMathMLStyling = false;
2063 0 : bool anyTextEmphasis = false;
2064 0 : uint8_t sstyScriptLevel = 0;
2065 0 : uint32_t mathFlags = 0;
2066 0 : gfx::ShapedTextFlags flags = gfx::ShapedTextFlags();
2067 0 : nsTextFrameUtils::Flags flags2 = nsTextFrameUtils::Flags::TEXT_NO_BREAKS;
2068 :
2069 0 : if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) {
2070 : flags2 |= nsTextFrameUtils::Flags::TEXT_INCOMING_WHITESPACE;
2071 : }
2072 0 : if (mCurrentRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) {
2073 : flags |= gfx::ShapedTextFlags::TEXT_INCOMING_ARABICCHAR;
2074 : }
2075 :
2076 0 : AutoTArray<int32_t,50> textBreakPoints;
2077 : TextRunUserData dummyData;
2078 : TextRunMappedFlow dummyMappedFlow;
2079 : TextRunMappedFlow* userMappedFlows;
2080 : TextRunUserData* userData;
2081 : TextRunUserData* userDataToDestroy;
2082 : // If the situation is particularly simple (and common) we don't need to
2083 : // allocate userData.
2084 0 : if (mMappedFlows.Length() == 1 && !mMappedFlows[0].mEndFrame &&
2085 0 : mMappedFlows[0].mStartFrame->GetContentOffset() == 0) {
2086 0 : userData = &dummyData;
2087 0 : userMappedFlows = &dummyMappedFlow;
2088 0 : userDataToDestroy = nullptr;
2089 0 : dummyData.mMappedFlowCount = mMappedFlows.Length();
2090 0 : dummyData.mLastFlowIndex = 0;
2091 : } else {
2092 0 : userData = CreateUserData(mMappedFlows.Length());
2093 0 : userMappedFlows = reinterpret_cast<TextRunMappedFlow*>(userData + 1);
2094 0 : userDataToDestroy = userData;
2095 : }
2096 :
2097 0 : uint32_t currentTransformedTextOffset = 0;
2098 :
2099 0 : uint32_t nextBreakIndex = 0;
2100 0 : nsTextFrame* nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex);
2101 0 : bool isSVG = nsSVGUtils::IsInSVGTextSubtree(mLineContainer);
2102 : bool enabledJustification =
2103 0 : (mLineContainer->StyleText()->mTextAlign == NS_STYLE_TEXT_ALIGN_JUSTIFY ||
2104 0 : mLineContainer->StyleText()->mTextAlignLast == NS_STYLE_TEXT_ALIGN_JUSTIFY);
2105 :
2106 0 : const nsStyleText* textStyle = nullptr;
2107 0 : const nsStyleFont* fontStyle = nullptr;
2108 0 : ComputedStyle* lastComputedStyle = nullptr;
2109 0 : for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
2110 0 : MappedFlow* mappedFlow = &mMappedFlows[i];
2111 0 : nsTextFrame* f = mappedFlow->mStartFrame;
2112 :
2113 0 : lastComputedStyle = f->Style();
2114 : // Detect use of text-transform or font-variant anywhere in the run
2115 0 : textStyle = f->StyleText();
2116 0 : if (NS_STYLE_TEXT_TRANSFORM_NONE != textStyle->mTextTransform ||
2117 : // text-combine-upright requires converting from full-width
2118 : // characters to non-full-width correspendent in some cases.
2119 : lastComputedStyle->IsTextCombined()) {
2120 0 : anyTextTransformStyle = true;
2121 : }
2122 0 : if (textStyle->HasTextEmphasis()) {
2123 0 : anyTextEmphasis = true;
2124 : }
2125 0 : flags |= GetSpacingFlags(f);
2126 : nsTextFrameUtils::CompressionMode compression =
2127 0 : GetCSSWhitespaceToCompressionMode(f, textStyle);
2128 0 : if ((enabledJustification || f->ShouldSuppressLineBreak()) &&
2129 0 : !textStyle->WhiteSpaceIsSignificant() && !isSVG) {
2130 : flags |= gfx::ShapedTextFlags::TEXT_ENABLE_SPACING;
2131 : }
2132 0 : fontStyle = f->StyleFont();
2133 0 : nsIFrame* parent = mLineContainer->GetParent();
2134 0 : if (NS_MATHML_MATHVARIANT_NONE != fontStyle->mMathVariant) {
2135 0 : if (NS_MATHML_MATHVARIANT_NORMAL != fontStyle->mMathVariant) {
2136 0 : anyMathMLStyling = true;
2137 : }
2138 0 : } else if (mLineContainer->GetStateBits() & NS_FRAME_IS_IN_SINGLE_CHAR_MI) {
2139 0 : flags2 |= nsTextFrameUtils::Flags::TEXT_IS_SINGLE_CHAR_MI;
2140 0 : anyMathMLStyling = true;
2141 : // Test for fontstyle attribute as StyleFont() may not be accurate
2142 : // To be consistent in terms of ignoring CSS style changes, fontweight
2143 : // gets checked too.
2144 0 : if (parent) {
2145 0 : nsIContent* content = parent->GetContent();
2146 0 : if (content && content->IsElement()) {
2147 0 : if (content->AsElement()->AttrValueIs(kNameSpaceID_None,
2148 : nsGkAtoms::fontstyle_,
2149 0 : NS_LITERAL_STRING("normal"),
2150 0 : eCaseMatters)) {
2151 0 : mathFlags |= MathMLTextRunFactory::MATH_FONT_STYLING_NORMAL;
2152 : }
2153 0 : if (content->AsElement()->AttrValueIs(kNameSpaceID_None,
2154 : nsGkAtoms::fontweight_,
2155 0 : NS_LITERAL_STRING("bold"),
2156 0 : eCaseMatters)) {
2157 0 : mathFlags |= MathMLTextRunFactory::MATH_FONT_WEIGHT_BOLD;
2158 : }
2159 : }
2160 : }
2161 : }
2162 0 : if (mLineContainer->HasAnyStateBits(TEXT_IS_IN_TOKEN_MATHML)) {
2163 : // All MathML tokens except <mtext> use 'math' script.
2164 0 : if (!(parent && parent->GetContent() &&
2165 0 : parent->GetContent()->IsMathMLElement(nsGkAtoms::mtext_))) {
2166 : flags |= gfx::ShapedTextFlags::TEXT_USE_MATH_SCRIPT;
2167 : }
2168 0 : nsIMathMLFrame* mathFrame = do_QueryFrame(parent);
2169 0 : if (mathFrame) {
2170 0 : nsPresentationData presData;
2171 0 : mathFrame->GetPresentationData(presData);
2172 0 : if (NS_MATHML_IS_DTLS_SET(presData.flags)) {
2173 0 : mathFlags |= MathMLTextRunFactory::MATH_FONT_FEATURE_DTLS;
2174 0 : anyMathMLStyling = true;
2175 : }
2176 : }
2177 : }
2178 0 : nsIFrame* child = mLineContainer;
2179 0 : uint8_t oldScriptLevel = 0;
2180 0 : while (parent &&
2181 0 : child->HasAnyStateBits(NS_FRAME_MATHML_SCRIPT_DESCENDANT)) {
2182 : // Reconstruct the script level ignoring any user overrides. It is
2183 : // calculated this way instead of using scriptlevel to ensure the
2184 : // correct ssty font feature setting is used even if the user sets a
2185 : // different (especially negative) scriptlevel.
2186 0 : nsIMathMLFrame* mathFrame= do_QueryFrame(parent);
2187 0 : if (mathFrame) {
2188 0 : sstyScriptLevel += mathFrame->ScriptIncrement(child);
2189 : }
2190 0 : if (sstyScriptLevel < oldScriptLevel) {
2191 : // overflow
2192 0 : sstyScriptLevel = UINT8_MAX;
2193 0 : break;
2194 : }
2195 0 : child = parent;
2196 0 : parent = parent->GetParent();
2197 0 : oldScriptLevel = sstyScriptLevel;
2198 : }
2199 0 : if (sstyScriptLevel) {
2200 0 : anyMathMLStyling = true;
2201 : }
2202 :
2203 : // Figure out what content is included in this flow.
2204 0 : nsIContent* content = f->GetContent();
2205 0 : const nsTextFragment* frag = content->GetText();
2206 0 : int32_t contentStart = mappedFlow->mStartFrame->GetContentOffset();
2207 0 : int32_t contentEnd = mappedFlow->GetContentEnd();
2208 0 : int32_t contentLength = contentEnd - contentStart;
2209 :
2210 0 : TextRunMappedFlow* newFlow = &userMappedFlows[i];
2211 0 : newFlow->mStartFrame = mappedFlow->mStartFrame;
2212 0 : newFlow->mDOMOffsetToBeforeTransformOffset = skipChars.GetOriginalCharCount() -
2213 0 : mappedFlow->mStartFrame->GetContentOffset();
2214 0 : newFlow->mContentLength = contentLength;
2215 :
2216 0 : while (nextBreakBeforeFrame && nextBreakBeforeFrame->GetContent() == content) {
2217 : textBreakPoints.AppendElement(
2218 0 : nextBreakBeforeFrame->GetContentOffset() + newFlow->mDOMOffsetToBeforeTransformOffset);
2219 0 : nextBreakBeforeFrame = GetNextBreakBeforeFrame(&nextBreakIndex);
2220 : }
2221 :
2222 : nsTextFrameUtils::Flags analysisFlags;
2223 0 : if (frag->Is2b()) {
2224 0 : NS_ASSERTION(mDoubleByteText, "Wrong buffer char size!");
2225 0 : char16_t* bufStart = static_cast<char16_t*>(aTextBuffer);
2226 0 : char16_t* bufEnd = nsTextFrameUtils::TransformText(
2227 0 : frag->Get2b() + contentStart, contentLength, bufStart,
2228 0 : compression, &mNextRunContextInfo, &skipChars, &analysisFlags);
2229 0 : aTextBuffer = bufEnd;
2230 0 : currentTransformedTextOffset = bufEnd - static_cast<const char16_t*>(textPtr);
2231 : } else {
2232 0 : if (mDoubleByteText) {
2233 : // Need to expand the text. First transform it into a temporary buffer,
2234 : // then expand.
2235 0 : AutoTArray<uint8_t,BIG_TEXT_NODE_SIZE> tempBuf;
2236 0 : uint8_t* bufStart = tempBuf.AppendElements(contentLength, fallible);
2237 0 : if (!bufStart) {
2238 0 : DestroyUserData(userDataToDestroy);
2239 0 : return nullptr;
2240 : }
2241 0 : uint8_t* end = nsTextFrameUtils::TransformText(
2242 0 : reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart, contentLength,
2243 0 : bufStart, compression, &mNextRunContextInfo, &skipChars, &analysisFlags);
2244 0 : aTextBuffer = ExpandBuffer(static_cast<char16_t*>(aTextBuffer),
2245 0 : tempBuf.Elements(), end - tempBuf.Elements());
2246 0 : currentTransformedTextOffset =
2247 0 : static_cast<char16_t*>(aTextBuffer) - static_cast<const char16_t*>(textPtr);
2248 : } else {
2249 0 : uint8_t* bufStart = static_cast<uint8_t*>(aTextBuffer);
2250 0 : uint8_t* end = nsTextFrameUtils::TransformText(
2251 0 : reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart, contentLength,
2252 0 : bufStart, compression, &mNextRunContextInfo, &skipChars, &analysisFlags);
2253 0 : aTextBuffer = end;
2254 0 : currentTransformedTextOffset = end - static_cast<const uint8_t*>(textPtr);
2255 : }
2256 : }
2257 0 : flags2 |= analysisFlags;
2258 : }
2259 :
2260 : void* finalUserData;
2261 0 : if (userData == &dummyData) {
2262 0 : flags2 |= nsTextFrameUtils::Flags::TEXT_IS_SIMPLE_FLOW;
2263 0 : userData = nullptr;
2264 0 : finalUserData = mMappedFlows[0].mStartFrame;
2265 : } else {
2266 : finalUserData = userData;
2267 : }
2268 :
2269 0 : uint32_t transformedLength = currentTransformedTextOffset;
2270 :
2271 : // Now build the textrun
2272 0 : nsTextFrame* firstFrame = mMappedFlows[0].mStartFrame;
2273 : float fontInflation;
2274 0 : if (mWhichTextRun == nsTextFrame::eNotInflated) {
2275 0 : fontInflation = 1.0f;
2276 : } else {
2277 0 : fontInflation = nsLayoutUtils::FontSizeInflationFor(firstFrame);
2278 : }
2279 :
2280 0 : gfxFontGroup* fontGroup = GetFontGroupForFrame(firstFrame, fontInflation);
2281 0 : if (!fontGroup) {
2282 0 : DestroyUserData(userDataToDestroy);
2283 : return nullptr;
2284 : }
2285 :
2286 0 : if (flags2 & nsTextFrameUtils::Flags::TEXT_HAS_TAB) {
2287 : flags |= gfx::ShapedTextFlags::TEXT_ENABLE_SPACING;
2288 : }
2289 0 : if (flags2 & nsTextFrameUtils::Flags::TEXT_HAS_SHY) {
2290 : flags |= gfx::ShapedTextFlags::TEXT_ENABLE_HYPHEN_BREAKS;
2291 : }
2292 0 : if (mBidiEnabled && (IS_LEVEL_RTL(firstFrame->GetEmbeddingLevel()))) {
2293 : flags |= gfx::ShapedTextFlags::TEXT_IS_RTL;
2294 : }
2295 0 : if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_WHITESPACE) {
2296 : flags2 |= nsTextFrameUtils::Flags::TEXT_TRAILING_WHITESPACE;
2297 : }
2298 0 : if (mNextRunContextInfo & nsTextFrameUtils::INCOMING_ARABICCHAR) {
2299 : flags |= gfx::ShapedTextFlags::TEXT_TRAILING_ARABICCHAR;
2300 : }
2301 : // ContinueTextRunAcrossFrames guarantees that it doesn't matter which
2302 : // frame's style is used, so we use a mixture of the first frame and
2303 : // last frame's style
2304 : flags |= nsLayoutUtils::GetTextRunFlagsForStyle(lastComputedStyle,
2305 : firstFrame->PresContext(), fontStyle, textStyle,
2306 0 : LetterSpacing(firstFrame, textStyle));
2307 : // XXX this is a bit of a hack. For performance reasons, if we're favouring
2308 : // performance over quality, don't try to get accurate glyph extents.
2309 0 : if (!(flags & gfx::ShapedTextFlags::TEXT_OPTIMIZE_SPEED)) {
2310 : flags |= gfx::ShapedTextFlags::TEXT_NEED_BOUNDING_BOX;
2311 : }
2312 :
2313 : // Convert linebreak coordinates to transformed string offsets
2314 0 : NS_ASSERTION(nextBreakIndex == mLineBreakBeforeFrames.Length(),
2315 : "Didn't find all the frames to break-before...");
2316 0 : gfxSkipCharsIterator iter(skipChars);
2317 0 : AutoTArray<uint32_t,50> textBreakPointsAfterTransform;
2318 0 : for (uint32_t i = 0; i < textBreakPoints.Length(); ++i) {
2319 0 : nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform,
2320 0 : iter.ConvertOriginalToSkipped(textBreakPoints[i]));
2321 : }
2322 0 : if (mStartOfLine) {
2323 : nsTextFrameUtils::AppendLineBreakOffset(&textBreakPointsAfterTransform,
2324 0 : transformedLength);
2325 : }
2326 :
2327 : // Setup factory chain
2328 0 : UniquePtr<nsTransformingTextRunFactory> transformingFactory;
2329 0 : if (anyTextTransformStyle) {
2330 : transformingFactory =
2331 0 : MakeUnique<nsCaseTransformTextRunFactory>(std::move(transformingFactory));
2332 : }
2333 0 : if (anyMathMLStyling) {
2334 : transformingFactory =
2335 0 : MakeUnique<MathMLTextRunFactory>(std::move(transformingFactory), mathFlags,
2336 0 : sstyScriptLevel, fontInflation);
2337 : }
2338 0 : nsTArray<RefPtr<nsTransformedCharStyle>> styles;
2339 0 : if (transformingFactory) {
2340 0 : iter.SetOriginalOffset(0);
2341 0 : for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
2342 0 : MappedFlow* mappedFlow = &mMappedFlows[i];
2343 : nsTextFrame* f;
2344 0 : ComputedStyle* sc = nullptr;
2345 0 : RefPtr<nsTransformedCharStyle> charStyle;
2346 0 : for (f = mappedFlow->mStartFrame; f != mappedFlow->mEndFrame;
2347 : f = f->GetNextContinuation()) {
2348 0 : uint32_t offset = iter.GetSkippedOffset();
2349 0 : iter.AdvanceOriginal(f->GetContentLength());
2350 0 : uint32_t end = iter.GetSkippedOffset();
2351 : // Text-combined frames have content-dependent transform, so we
2352 : // want to create new nsTransformedCharStyle for them anyway.
2353 0 : if (sc != f->Style() || sc->IsTextCombined()) {
2354 0 : sc = f->Style();
2355 0 : charStyle = new nsTransformedCharStyle(sc, f->PresContext());
2356 0 : if (sc->IsTextCombined() && f->CountGraphemeClusters() > 1) {
2357 0 : charStyle->mForceNonFullWidth = true;
2358 : }
2359 : }
2360 : uint32_t j;
2361 0 : for (j = offset; j < end; ++j) {
2362 0 : styles.AppendElement(charStyle);
2363 : }
2364 : }
2365 : }
2366 0 : flags2 |= nsTextFrameUtils::Flags::TEXT_IS_TRANSFORMED;
2367 0 : NS_ASSERTION(iter.GetSkippedOffset() == transformedLength,
2368 : "We didn't cover all the characters in the text run!");
2369 : }
2370 :
2371 0 : RefPtr<gfxTextRun> textRun;
2372 : gfxTextRunFactory::Parameters params =
2373 0 : { mDrawTarget, finalUserData, &skipChars,
2374 0 : textBreakPointsAfterTransform.Elements(),
2375 0 : uint32_t(textBreakPointsAfterTransform.Length()),
2376 0 : int32_t(firstFrame->PresContext()->AppUnitsPerDevPixel())};
2377 :
2378 0 : if (mDoubleByteText) {
2379 0 : const char16_t* text = static_cast<const char16_t*>(textPtr);
2380 0 : if (transformingFactory) {
2381 0 : textRun = transformingFactory->MakeTextRun(text, transformedLength,
2382 : ¶ms, fontGroup, flags, flags2,
2383 0 : std::move(styles), true);
2384 0 : if (textRun) {
2385 : // ownership of the factory has passed to the textrun
2386 : // TODO: bug 1285316: clean up ownership transfer from the factory to
2387 : // the textrun
2388 0 : Unused << transformingFactory.release();
2389 : }
2390 : } else {
2391 0 : textRun = fontGroup->MakeTextRun(text, transformedLength, ¶ms,
2392 0 : flags, flags2, mMissingFonts);
2393 : }
2394 : } else {
2395 0 : const uint8_t* text = static_cast<const uint8_t*>(textPtr);
2396 0 : flags |= gfx::ShapedTextFlags::TEXT_IS_8BIT;
2397 0 : if (transformingFactory) {
2398 0 : textRun = transformingFactory->MakeTextRun(text, transformedLength,
2399 : ¶ms, fontGroup, flags, flags2,
2400 0 : std::move(styles), true);
2401 0 : if (textRun) {
2402 : // ownership of the factory has passed to the textrun
2403 : // TODO: bug 1285316: clean up ownership transfer from the factory to
2404 : // the textrun
2405 0 : Unused << transformingFactory.release();
2406 : }
2407 : } else {
2408 0 : textRun = fontGroup->MakeTextRun(text, transformedLength, ¶ms,
2409 0 : flags, flags2, mMissingFonts);
2410 : }
2411 : }
2412 0 : if (!textRun) {
2413 0 : DestroyUserData(userDataToDestroy);
2414 : return nullptr;
2415 : }
2416 :
2417 : // We have to set these up after we've created the textrun, because
2418 : // the breaks may be stored in the textrun during this very call.
2419 : // This is a bit annoying because it requires another loop over the frames
2420 : // making up the textrun, but I don't see a way to avoid this.
2421 0 : SetupBreakSinksForTextRun(textRun.get(), textPtr);
2422 :
2423 0 : if (anyTextEmphasis) {
2424 0 : SetupTextEmphasisForTextRun(textRun.get(), textPtr);
2425 : }
2426 :
2427 0 : if (mSkipIncompleteTextRuns) {
2428 0 : mSkipIncompleteTextRuns = !TextContainsLineBreakerWhiteSpace(textPtr,
2429 0 : transformedLength, mDoubleByteText);
2430 : // Since we're doing to destroy the user data now, avoid a dangling
2431 : // pointer. Strictly speaking we don't need to do this since it should
2432 : // not be used (since this textrun will not be used and will be
2433 : // itself deleted soon), but it's always better to not have dangling
2434 : // pointers around.
2435 0 : textRun->SetUserData(nullptr);
2436 0 : DestroyUserData(userDataToDestroy);
2437 : return nullptr;
2438 : }
2439 :
2440 : // Actually wipe out the textruns associated with the mapped frames and associate
2441 : // those frames with this text run.
2442 0 : AssignTextRun(textRun.get(), fontInflation);
2443 : return textRun.forget();
2444 : }
2445 :
2446 : // This is a cut-down version of BuildTextRunForFrames used to set up
2447 : // context for the line-breaker, when the textrun has already been created.
2448 : // So it does the same walk over the mMappedFlows, but doesn't actually
2449 : // build a new textrun.
2450 : bool
2451 0 : BuildTextRunsScanner::SetupLineBreakerContext(gfxTextRun *aTextRun)
2452 : {
2453 0 : AutoTArray<uint8_t,BIG_TEXT_NODE_SIZE> buffer;
2454 0 : uint32_t bufferSize = mMaxTextLength*(mDoubleByteText ? 2 : 1);
2455 0 : if (bufferSize < mMaxTextLength || bufferSize == UINT32_MAX) {
2456 : return false;
2457 : }
2458 0 : void *textPtr = buffer.AppendElements(bufferSize, fallible);
2459 0 : if (!textPtr) {
2460 : return false;
2461 : }
2462 :
2463 0 : gfxSkipChars skipChars;
2464 :
2465 : TextRunUserData dummyData;
2466 : TextRunMappedFlow dummyMappedFlow;
2467 : TextRunMappedFlow* userMappedFlows;
2468 : TextRunUserData* userData;
2469 : TextRunUserData* userDataToDestroy;
2470 : // If the situation is particularly simple (and common) we don't need to
2471 : // allocate userData.
2472 0 : if (mMappedFlows.Length() == 1 && !mMappedFlows[0].mEndFrame &&
2473 0 : mMappedFlows[0].mStartFrame->GetContentOffset() == 0) {
2474 : userData = &dummyData;
2475 : userMappedFlows = &dummyMappedFlow;
2476 : userDataToDestroy = nullptr;
2477 : dummyData.mMappedFlowCount = mMappedFlows.Length();
2478 : dummyData.mLastFlowIndex = 0;
2479 : } else {
2480 0 : userData = CreateUserData(mMappedFlows.Length());
2481 0 : userMappedFlows = reinterpret_cast<TextRunMappedFlow*>(userData + 1);
2482 0 : userDataToDestroy = userData;
2483 : }
2484 :
2485 0 : const nsStyleText* textStyle = nullptr;
2486 0 : for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
2487 0 : MappedFlow* mappedFlow = &mMappedFlows[i];
2488 0 : nsTextFrame* f = mappedFlow->mStartFrame;
2489 :
2490 0 : textStyle = f->StyleText();
2491 : nsTextFrameUtils::CompressionMode compression =
2492 0 : GetCSSWhitespaceToCompressionMode(f, textStyle);
2493 :
2494 : // Figure out what content is included in this flow.
2495 0 : nsIContent* content = f->GetContent();
2496 0 : const nsTextFragment* frag = content->GetText();
2497 0 : int32_t contentStart = mappedFlow->mStartFrame->GetContentOffset();
2498 0 : int32_t contentEnd = mappedFlow->GetContentEnd();
2499 0 : int32_t contentLength = contentEnd - contentStart;
2500 :
2501 0 : TextRunMappedFlow* newFlow = &userMappedFlows[i];
2502 0 : newFlow->mStartFrame = mappedFlow->mStartFrame;
2503 0 : newFlow->mDOMOffsetToBeforeTransformOffset = skipChars.GetOriginalCharCount() -
2504 0 : mappedFlow->mStartFrame->GetContentOffset();
2505 0 : newFlow->mContentLength = contentLength;
2506 :
2507 : nsTextFrameUtils::Flags analysisFlags;
2508 0 : if (frag->Is2b()) {
2509 0 : NS_ASSERTION(mDoubleByteText, "Wrong buffer char size!");
2510 0 : char16_t* bufStart = static_cast<char16_t*>(textPtr);
2511 0 : char16_t* bufEnd = nsTextFrameUtils::TransformText(
2512 0 : frag->Get2b() + contentStart, contentLength, bufStart,
2513 0 : compression, &mNextRunContextInfo, &skipChars, &analysisFlags);
2514 0 : textPtr = bufEnd;
2515 : } else {
2516 0 : if (mDoubleByteText) {
2517 : // Need to expand the text. First transform it into a temporary buffer,
2518 : // then expand.
2519 0 : AutoTArray<uint8_t,BIG_TEXT_NODE_SIZE> tempBuf;
2520 0 : uint8_t* bufStart = tempBuf.AppendElements(contentLength, fallible);
2521 0 : if (!bufStart) {
2522 0 : DestroyUserData(userDataToDestroy);
2523 0 : return false;
2524 : }
2525 0 : uint8_t* end = nsTextFrameUtils::TransformText(
2526 0 : reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart, contentLength,
2527 0 : bufStart, compression, &mNextRunContextInfo, &skipChars, &analysisFlags);
2528 0 : textPtr = ExpandBuffer(static_cast<char16_t*>(textPtr),
2529 0 : tempBuf.Elements(), end - tempBuf.Elements());
2530 : } else {
2531 0 : uint8_t* bufStart = static_cast<uint8_t*>(textPtr);
2532 0 : uint8_t* end = nsTextFrameUtils::TransformText(
2533 0 : reinterpret_cast<const uint8_t*>(frag->Get1b()) + contentStart, contentLength,
2534 0 : bufStart, compression, &mNextRunContextInfo, &skipChars, &analysisFlags);
2535 0 : textPtr = end;
2536 : }
2537 : }
2538 : }
2539 :
2540 : // We have to set these up after we've created the textrun, because
2541 : // the breaks may be stored in the textrun during this very call.
2542 : // This is a bit annoying because it requires another loop over the frames
2543 : // making up the textrun, but I don't see a way to avoid this.
2544 0 : SetupBreakSinksForTextRun(aTextRun, buffer.Elements());
2545 :
2546 0 : DestroyUserData(userDataToDestroy);
2547 :
2548 0 : return true;
2549 : }
2550 :
2551 : static bool
2552 0 : HasCompressedLeadingWhitespace(nsTextFrame* aFrame, const nsStyleText* aStyleText,
2553 : int32_t aContentEndOffset,
2554 : const gfxSkipCharsIterator& aIterator)
2555 : {
2556 0 : if (!aIterator.IsOriginalCharSkipped())
2557 : return false;
2558 :
2559 0 : gfxSkipCharsIterator iter = aIterator;
2560 0 : int32_t frameContentOffset = aFrame->GetContentOffset();
2561 0 : const nsTextFragment* frag = aFrame->GetContent()->GetText();
2562 0 : while (frameContentOffset < aContentEndOffset && iter.IsOriginalCharSkipped()) {
2563 0 : if (IsTrimmableSpace(frag, frameContentOffset, aStyleText))
2564 : return true;
2565 0 : ++frameContentOffset;
2566 0 : iter.AdvanceOriginal(1);
2567 : }
2568 : return false;
2569 : }
2570 :
2571 : void
2572 0 : BuildTextRunsScanner::SetupBreakSinksForTextRun(gfxTextRun* aTextRun,
2573 : const void* aTextPtr)
2574 : {
2575 : using mozilla::intl::LineBreaker;
2576 :
2577 : // for word-break style
2578 0 : switch (mLineContainer->StyleText()->mWordBreak) {
2579 : case NS_STYLE_WORDBREAK_BREAK_ALL:
2580 0 : mLineBreaker.SetWordBreak(LineBreaker::kWordBreak_BreakAll);
2581 : break;
2582 : case NS_STYLE_WORDBREAK_KEEP_ALL:
2583 0 : mLineBreaker.SetWordBreak(LineBreaker::kWordBreak_KeepAll);
2584 : break;
2585 : default:
2586 0 : mLineBreaker.SetWordBreak(LineBreaker::kWordBreak_Normal);
2587 : break;
2588 : }
2589 :
2590 : // textruns have uniform language
2591 0 : const nsStyleFont *styleFont = mMappedFlows[0].mStartFrame->StyleFont();
2592 : // We should only use a language for hyphenation if it was specified
2593 : // explicitly.
2594 : nsAtom* hyphenationLanguage =
2595 0 : styleFont->mExplicitLanguage ? styleFont->mLanguage.get() : nullptr;
2596 : // We keep this pointed at the skip-chars data for the current mappedFlow.
2597 : // This lets us cheaply check whether the flow has compressed initial
2598 : // whitespace...
2599 0 : gfxSkipCharsIterator iter(aTextRun->GetSkipChars());
2600 :
2601 0 : for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
2602 0 : MappedFlow* mappedFlow = &mMappedFlows[i];
2603 0 : uint32_t offset = iter.GetSkippedOffset();
2604 0 : gfxSkipCharsIterator iterNext = iter;
2605 0 : iterNext.AdvanceOriginal(mappedFlow->GetContentEnd() -
2606 0 : mappedFlow->mStartFrame->GetContentOffset());
2607 :
2608 : UniquePtr<BreakSink>* breakSink =
2609 0 : mBreakSinks.AppendElement(MakeUnique<BreakSink>(aTextRun, mDrawTarget, offset));
2610 0 : if (!breakSink || !*breakSink)
2611 0 : return;
2612 :
2613 0 : uint32_t length = iterNext.GetSkippedOffset() - offset;
2614 0 : uint32_t flags = 0;
2615 0 : nsIFrame* initialBreakController = mappedFlow->mAncestorControllingInitialBreak;
2616 0 : if (!initialBreakController) {
2617 0 : initialBreakController = mLineContainer;
2618 : }
2619 0 : if (!initialBreakController->StyleText()->
2620 0 : WhiteSpaceCanWrap(initialBreakController)) {
2621 0 : flags |= nsLineBreaker::BREAK_SUPPRESS_INITIAL;
2622 : }
2623 0 : nsTextFrame* startFrame = mappedFlow->mStartFrame;
2624 0 : const nsStyleText* textStyle = startFrame->StyleText();
2625 0 : if (!textStyle->WhiteSpaceCanWrap(startFrame)) {
2626 0 : flags |= nsLineBreaker::BREAK_SUPPRESS_INSIDE;
2627 : }
2628 0 : if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_NO_BREAKS) {
2629 0 : flags |= nsLineBreaker::BREAK_SKIP_SETTING_NO_BREAKS;
2630 : }
2631 0 : if (textStyle->mTextTransform == NS_STYLE_TEXT_TRANSFORM_CAPITALIZE) {
2632 0 : flags |= nsLineBreaker::BREAK_NEED_CAPITALIZATION;
2633 : }
2634 0 : if (textStyle->mHyphens == StyleHyphens::Auto) {
2635 0 : flags |= nsLineBreaker::BREAK_USE_AUTO_HYPHENATION;
2636 : }
2637 :
2638 0 : if (HasCompressedLeadingWhitespace(startFrame, textStyle,
2639 : mappedFlow->GetContentEnd(), iter)) {
2640 0 : mLineBreaker.AppendInvisibleWhitespace(flags);
2641 : }
2642 :
2643 0 : if (length > 0) {
2644 : BreakSink* sink =
2645 0 : mSkipIncompleteTextRuns ? nullptr : (*breakSink).get();
2646 0 : if (mDoubleByteText) {
2647 0 : const char16_t* text = reinterpret_cast<const char16_t*>(aTextPtr);
2648 0 : mLineBreaker.AppendText(hyphenationLanguage, text + offset,
2649 0 : length, flags, sink);
2650 : } else {
2651 0 : const uint8_t* text = reinterpret_cast<const uint8_t*>(aTextPtr);
2652 0 : mLineBreaker.AppendText(hyphenationLanguage, text + offset,
2653 0 : length, flags, sink);
2654 : }
2655 : }
2656 :
2657 0 : iter = iterNext;
2658 : }
2659 : }
2660 :
2661 : static bool
2662 0 : MayCharacterHaveEmphasisMark(uint32_t aCh)
2663 : {
2664 0 : auto category = unicode::GetGeneralCategory(aCh);
2665 : // Comparing an unsigned variable against zero is a compile error,
2666 : // so we use static assert here to ensure we really don't need to
2667 : // compare it with the given constant.
2668 : static_assert(IsUnsigned<decltype(category)>::value &&
2669 : HB_UNICODE_GENERAL_CATEGORY_CONTROL == 0,
2670 : "if this constant is not zero, or category is signed, "
2671 : "we need to explicitly do the comparison below");
2672 0 : return !(category <= HB_UNICODE_GENERAL_CATEGORY_UNASSIGNED ||
2673 0 : (category >= HB_UNICODE_GENERAL_CATEGORY_LINE_SEPARATOR &&
2674 0 : category <= HB_UNICODE_GENERAL_CATEGORY_SPACE_SEPARATOR));
2675 : }
2676 :
2677 : static bool
2678 0 : MayCharacterHaveEmphasisMark(uint8_t aCh)
2679 : {
2680 : // 0x00~0x1f and 0x7f~0x9f are in category Cc
2681 : // 0x20 and 0xa0 are in category Zs
2682 0 : bool result = !(aCh <= 0x20 || (aCh >= 0x7f && aCh <= 0xa0));
2683 0 : MOZ_ASSERT(result == MayCharacterHaveEmphasisMark(uint32_t(aCh)),
2684 : "result for uint8_t should match result for uint32_t");
2685 0 : return result;
2686 : }
2687 :
2688 : void
2689 0 : BuildTextRunsScanner::SetupTextEmphasisForTextRun(gfxTextRun* aTextRun,
2690 : const void* aTextPtr)
2691 : {
2692 0 : if (!mDoubleByteText) {
2693 0 : auto text = reinterpret_cast<const uint8_t*>(aTextPtr);
2694 0 : for (auto i : IntegerRange(aTextRun->GetLength())) {
2695 0 : if (!MayCharacterHaveEmphasisMark(text[i])) {
2696 0 : aTextRun->SetNoEmphasisMark(i);
2697 : }
2698 : }
2699 : } else {
2700 0 : auto text = reinterpret_cast<const char16_t*>(aTextPtr);
2701 0 : auto length = aTextRun->GetLength();
2702 0 : for (size_t i = 0; i < length; ++i) {
2703 0 : if (NS_IS_HIGH_SURROGATE(text[i]) && i + 1 < length &&
2704 0 : NS_IS_LOW_SURROGATE(text[i + 1])) {
2705 0 : uint32_t ch = SURROGATE_TO_UCS4(text[i], text[i + 1]);
2706 0 : if (!MayCharacterHaveEmphasisMark(ch)) {
2707 0 : aTextRun->SetNoEmphasisMark(i);
2708 0 : aTextRun->SetNoEmphasisMark(i + 1);
2709 : }
2710 : ++i;
2711 : } else {
2712 0 : if (!MayCharacterHaveEmphasisMark(uint32_t(text[i]))) {
2713 0 : aTextRun->SetNoEmphasisMark(i);
2714 : }
2715 : }
2716 : }
2717 : }
2718 0 : }
2719 :
2720 : // Find the flow corresponding to aContent in aUserData
2721 : static inline TextRunMappedFlow*
2722 0 : FindFlowForContent(TextRunUserData* aUserData, nsIContent* aContent,
2723 : TextRunMappedFlow* userMappedFlows)
2724 : {
2725 : // Find the flow that contains us
2726 0 : int32_t i = aUserData->mLastFlowIndex;
2727 0 : int32_t delta = 1;
2728 0 : int32_t sign = 1;
2729 : // Search starting at the current position and examine close-by
2730 : // positions first, moving further and further away as we go.
2731 0 : while (i >= 0 && uint32_t(i) < aUserData->mMappedFlowCount) {
2732 0 : TextRunMappedFlow* flow = &userMappedFlows[i];
2733 0 : if (flow->mStartFrame->GetContent() == aContent) {
2734 : return flow;
2735 : }
2736 :
2737 0 : i += delta;
2738 0 : sign = -sign;
2739 0 : delta = -delta + sign;
2740 : }
2741 :
2742 : // We ran into an array edge. Add |delta| to |i| once more to get
2743 : // back to the side where we still need to search, then step in
2744 : // the |sign| direction.
2745 0 : i += delta;
2746 0 : if (sign > 0) {
2747 0 : for (; i < int32_t(aUserData->mMappedFlowCount); ++i) {
2748 0 : TextRunMappedFlow* flow = &userMappedFlows[i];
2749 0 : if (flow->mStartFrame->GetContent() == aContent) {
2750 : return flow;
2751 : }
2752 : }
2753 : } else {
2754 0 : for (; i >= 0; --i) {
2755 0 : TextRunMappedFlow* flow = &userMappedFlows[i];
2756 0 : if (flow->mStartFrame->GetContent() == aContent) {
2757 : return flow;
2758 : }
2759 : }
2760 : }
2761 :
2762 : return nullptr;
2763 : }
2764 :
2765 : void
2766 0 : BuildTextRunsScanner::AssignTextRun(gfxTextRun* aTextRun, float aInflation)
2767 : {
2768 0 : for (uint32_t i = 0; i < mMappedFlows.Length(); ++i) {
2769 0 : MappedFlow* mappedFlow = &mMappedFlows[i];
2770 0 : nsTextFrame* startFrame = mappedFlow->mStartFrame;
2771 0 : nsTextFrame* endFrame = mappedFlow->mEndFrame;
2772 : nsTextFrame* f;
2773 0 : for (f = startFrame; f != endFrame; f = f->GetNextContinuation()) {
2774 : #ifdef DEBUG_roc
2775 : if (f->GetTextRun(mWhichTextRun)) {
2776 : gfxTextRun* textRun = f->GetTextRun(mWhichTextRun);
2777 : if (textRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_IS_SIMPLE_FLOW) {
2778 : if (mMappedFlows[0].mStartFrame != GetFrameForSimpleFlow(textRun)) {
2779 : NS_WARNING("REASSIGNING SIMPLE FLOW TEXT RUN!");
2780 : }
2781 : } else {
2782 : auto userData = static_cast<TextRunUserData*>(aTextRun->GetUserData());
2783 : TextRunMappedFlow* userMappedFlows = GetMappedFlows(aTextRun);
2784 : if (userData->mMappedFlowCount >= mMappedFlows.Length() ||
2785 : userMappedFlows[userData->mMappedFlowCount - 1].mStartFrame !=
2786 : mMappedFlows[userdata->mMappedFlowCount - 1].mStartFrame) {
2787 : NS_WARNING("REASSIGNING MULTIFLOW TEXT RUN (not append)!");
2788 : }
2789 : }
2790 : }
2791 : #endif
2792 :
2793 0 : gfxTextRun* oldTextRun = f->GetTextRun(mWhichTextRun);
2794 0 : if (oldTextRun) {
2795 0 : nsTextFrame* firstFrame = nullptr;
2796 0 : uint32_t startOffset = 0;
2797 0 : if (oldTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_IS_SIMPLE_FLOW) {
2798 0 : firstFrame = GetFrameForSimpleFlow(oldTextRun);
2799 : } else {
2800 0 : auto userData = static_cast<TextRunUserData*>(oldTextRun->GetUserData());
2801 0 : TextRunMappedFlow* userMappedFlows = GetMappedFlows(oldTextRun);
2802 0 : firstFrame = userMappedFlows[0].mStartFrame;
2803 0 : if (MOZ_UNLIKELY(f != firstFrame)) {
2804 : TextRunMappedFlow* flow =
2805 0 : FindFlowForContent(userData, f->GetContent(), userMappedFlows);
2806 0 : if (flow) {
2807 0 : startOffset = flow->mDOMOffsetToBeforeTransformOffset;
2808 : } else {
2809 0 : NS_ERROR("Can't find flow containing frame 'f'");
2810 : }
2811 : }
2812 : }
2813 :
2814 : // Optimization: if |f| is the first frame in the flow then there are no
2815 : // prev-continuations that use |oldTextRun|.
2816 0 : nsTextFrame* clearFrom = nullptr;
2817 0 : if (MOZ_UNLIKELY(f != firstFrame)) {
2818 : // If all the frames in the mapped flow starting at |f| (inclusive)
2819 : // are empty then we let the prev-continuations keep the old text run.
2820 0 : gfxSkipCharsIterator iter(oldTextRun->GetSkipChars(), startOffset, f->GetContentOffset());
2821 0 : uint32_t textRunOffset = iter.ConvertOriginalToSkipped(f->GetContentOffset());
2822 0 : clearFrom = textRunOffset == oldTextRun->GetLength() ? f : nullptr;
2823 : }
2824 0 : f->ClearTextRun(clearFrom, mWhichTextRun);
2825 :
2826 : #ifdef DEBUG
2827 0 : if (firstFrame && !firstFrame->GetTextRun(mWhichTextRun)) {
2828 : // oldTextRun was destroyed - assert that we don't reference it.
2829 0 : for (uint32_t j = 0; j < mBreakSinks.Length(); ++j) {
2830 0 : NS_ASSERTION(oldTextRun != mBreakSinks[j]->mTextRun,
2831 : "destroyed text run is still in use");
2832 : }
2833 : }
2834 : #endif
2835 : }
2836 0 : f->SetTextRun(aTextRun, mWhichTextRun, aInflation);
2837 : }
2838 : // Set this bit now; we can't set it any earlier because
2839 : // f->ClearTextRun() might clear it out.
2840 : nsFrameState whichTextRunState =
2841 0 : startFrame->GetTextRun(nsTextFrame::eInflated) == aTextRun
2842 0 : ? TEXT_IN_TEXTRUN_USER_DATA
2843 0 : : TEXT_IN_UNINFLATED_TEXTRUN_USER_DATA;
2844 0 : startFrame->AddStateBits(whichTextRunState);
2845 : }
2846 0 : }
2847 :
2848 0 : NS_QUERYFRAME_HEAD(nsTextFrame)
2849 : NS_QUERYFRAME_ENTRY(nsTextFrame)
2850 0 : NS_QUERYFRAME_TAIL_INHERITING(nsFrame)
2851 :
2852 : gfxSkipCharsIterator
2853 0 : nsTextFrame::EnsureTextRun(TextRunType aWhichTextRun,
2854 : DrawTarget* aRefDrawTarget,
2855 : nsIFrame* aLineContainer,
2856 : const nsLineList::iterator* aLine,
2857 : uint32_t* aFlowEndInTextRun)
2858 : {
2859 0 : gfxTextRun *textRun = GetTextRun(aWhichTextRun);
2860 0 : if (!textRun || (aLine && (*aLine)->GetInvalidateTextRuns())) {
2861 0 : RefPtr<DrawTarget> refDT = aRefDrawTarget;
2862 0 : if (!refDT) {
2863 0 : refDT = CreateReferenceDrawTarget(this);
2864 : }
2865 0 : if (refDT) {
2866 0 : BuildTextRuns(refDT, this, aLineContainer, aLine, aWhichTextRun);
2867 : }
2868 0 : textRun = GetTextRun(aWhichTextRun);
2869 0 : if (!textRun) {
2870 : // A text run was not constructed for this frame. This is bad. The caller
2871 : // will check mTextRun.
2872 : return gfxSkipCharsIterator(gfxPlatform::
2873 0 : GetPlatform()->EmptySkipChars(), 0);
2874 : }
2875 0 : TabWidthStore* tabWidths = GetProperty(TabWidthProperty());
2876 0 : if (tabWidths && tabWidths->mValidForContentOffset != GetContentOffset()) {
2877 0 : DeleteProperty(TabWidthProperty());
2878 : }
2879 : }
2880 :
2881 0 : if (textRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_IS_SIMPLE_FLOW) {
2882 0 : if (aFlowEndInTextRun) {
2883 0 : *aFlowEndInTextRun = textRun->GetLength();
2884 : }
2885 0 : return gfxSkipCharsIterator(textRun->GetSkipChars(), 0, mContentOffset);
2886 : }
2887 :
2888 0 : auto userData = static_cast<TextRunUserData*>(textRun->GetUserData());
2889 0 : TextRunMappedFlow* userMappedFlows = GetMappedFlows(textRun);
2890 : TextRunMappedFlow* flow =
2891 0 : FindFlowForContent(userData, mContent, userMappedFlows);
2892 0 : if (flow) {
2893 : // Since textruns can only contain one flow for a given content element,
2894 : // this must be our flow.
2895 0 : uint32_t flowIndex = flow - userMappedFlows;
2896 0 : userData->mLastFlowIndex = flowIndex;
2897 : gfxSkipCharsIterator iter(textRun->GetSkipChars(),
2898 0 : flow->mDOMOffsetToBeforeTransformOffset, mContentOffset);
2899 0 : if (aFlowEndInTextRun) {
2900 0 : if (flowIndex + 1 < userData->mMappedFlowCount) {
2901 0 : gfxSkipCharsIterator end(textRun->GetSkipChars());
2902 0 : *aFlowEndInTextRun = end.ConvertOriginalToSkipped(
2903 0 : flow[1].mStartFrame->GetContentOffset() + flow[1].mDOMOffsetToBeforeTransformOffset);
2904 : } else {
2905 0 : *aFlowEndInTextRun = textRun->GetLength();
2906 : }
2907 : }
2908 0 : return iter;
2909 : }
2910 :
2911 0 : NS_ERROR("Can't find flow containing this frame???");
2912 0 : return gfxSkipCharsIterator(gfxPlatform::GetPlatform()->EmptySkipChars(), 0);
2913 : }
2914 :
2915 : static uint32_t
2916 0 : GetEndOfTrimmedText(const nsTextFragment* aFrag, const nsStyleText* aStyleText,
2917 : uint32_t aStart, uint32_t aEnd,
2918 : gfxSkipCharsIterator* aIterator)
2919 : {
2920 0 : aIterator->SetSkippedOffset(aEnd);
2921 0 : while (aIterator->GetSkippedOffset() > aStart) {
2922 0 : aIterator->AdvanceSkipped(-1);
2923 0 : if (!IsTrimmableSpace(aFrag, aIterator->GetOriginalOffset(), aStyleText))
2924 0 : return aIterator->GetSkippedOffset() + 1;
2925 : }
2926 : return aStart;
2927 : }
2928 :
2929 : nsTextFrame::TrimmedOffsets
2930 0 : nsTextFrame::GetTrimmedOffsets(const nsTextFragment* aFrag,
2931 : bool aTrimAfter, bool aPostReflow) const
2932 : {
2933 0 : NS_ASSERTION(mTextRun, "Need textrun here");
2934 0 : if (aPostReflow) {
2935 : // This should not be used during reflow. We need our TEXT_REFLOW_FLAGS
2936 : // to be set correctly. If our parent wasn't reflowed due to the frame
2937 : // tree being too deep then the return value doesn't matter.
2938 0 : NS_ASSERTION(!(GetStateBits() & NS_FRAME_FIRST_REFLOW) ||
2939 : (GetParent()->GetStateBits() &
2940 : NS_FRAME_TOO_DEEP_IN_FRAME_TREE),
2941 : "Can only call this on frames that have been reflowed");
2942 0 : NS_ASSERTION(!(GetStateBits() & NS_FRAME_IN_REFLOW),
2943 : "Can only call this on frames that are not being reflowed");
2944 : }
2945 :
2946 0 : TrimmedOffsets offsets = { GetContentOffset(), GetContentLength() };
2947 0 : const nsStyleText* textStyle = StyleText();
2948 : // Note that pre-line newlines should still allow us to trim spaces
2949 : // for display
2950 0 : if (textStyle->WhiteSpaceIsSignificant())
2951 0 : return offsets;
2952 :
2953 0 : if (!aPostReflow || (GetStateBits() & TEXT_START_OF_LINE)) {
2954 : int32_t whitespaceCount =
2955 0 : GetTrimmableWhitespaceCount(aFrag,
2956 0 : offsets.mStart, offsets.mLength, 1);
2957 0 : offsets.mStart += whitespaceCount;
2958 0 : offsets.mLength -= whitespaceCount;
2959 : }
2960 :
2961 0 : if (aTrimAfter && (!aPostReflow || (GetStateBits() & TEXT_END_OF_LINE))) {
2962 : // This treats a trailing 'pre-line' newline as trimmable. That's fine,
2963 : // it's actually what we want since we want whitespace before it to
2964 : // be trimmed.
2965 : int32_t whitespaceCount =
2966 0 : GetTrimmableWhitespaceCount(aFrag,
2967 0 : offsets.GetEnd() - 1, offsets.mLength, -1);
2968 0 : offsets.mLength -= whitespaceCount;
2969 : }
2970 0 : return offsets;
2971 : }
2972 :
2973 0 : static bool IsJustifiableCharacter(const nsStyleText* aTextStyle,
2974 : const nsTextFragment* aFrag, int32_t aPos,
2975 : bool aLangIsCJ)
2976 : {
2977 0 : NS_ASSERTION(aPos >= 0, "negative position?!");
2978 :
2979 0 : StyleTextJustify justifyStyle = aTextStyle->mTextJustify;
2980 0 : if (justifyStyle == StyleTextJustify::None) {
2981 : return false;
2982 : }
2983 :
2984 0 : char16_t ch = aFrag->CharAt(aPos);
2985 0 : if (ch == '\n' || ch == '\t' || ch == '\r') {
2986 : return true;
2987 : }
2988 0 : if (ch == ' ' || ch == CH_NBSP) {
2989 : // Don't justify spaces that are combined with diacriticals
2990 0 : if (!aFrag->Is2b()) {
2991 : return true;
2992 : }
2993 0 : return !nsTextFrameUtils::IsSpaceCombiningSequenceTail(
2994 0 : aFrag->Get2b() + aPos + 1, aFrag->GetLength() - (aPos + 1));
2995 : }
2996 :
2997 0 : if (justifyStyle == StyleTextJustify::InterCharacter) {
2998 : return true;
2999 0 : } else if (justifyStyle == StyleTextJustify::InterWord) {
3000 : return false;
3001 : }
3002 :
3003 : // text-justify: auto
3004 0 : if (ch < 0x2150u) {
3005 : return false;
3006 : }
3007 0 : if (aLangIsCJ) {
3008 0 : if ((0x2150u <= ch && ch <= 0x22ffu) || // Number Forms, Arrows, Mathematical Operators
3009 0 : (0x2460u <= ch && ch <= 0x24ffu) || // Enclosed Alphanumerics
3010 0 : (0x2580u <= ch && ch <= 0x27bfu) || // Block Elements, Geometric Shapes, Miscellaneous Symbols, Dingbats
3011 0 : (0x27f0u <= ch && ch <= 0x2bffu) || // Supplemental Arrows-A, Braille Patterns, Supplemental Arrows-B,
3012 : // Miscellaneous Mathematical Symbols-B, Supplemental Mathematical Operators,
3013 : // Miscellaneous Symbols and Arrows
3014 0 : (0x2e80u <= ch && ch <= 0x312fu) || // CJK Radicals Supplement, CJK Radicals Supplement,
3015 : // Ideographic Description Characters, CJK Symbols and Punctuation,
3016 : // Hiragana, Katakana, Bopomofo
3017 0 : (0x3190u <= ch && ch <= 0xabffu) || // Kanbun, Bopomofo Extended, Katakana Phonetic Extensions,
3018 : // Enclosed CJK Letters and Months, CJK Compatibility,
3019 : // CJK Unified Ideographs Extension A, Yijing Hexagram Symbols,
3020 : // CJK Unified Ideographs, Yi Syllables, Yi Radicals
3021 0 : (0xf900u <= ch && ch <= 0xfaffu) || // CJK Compatibility Ideographs
3022 0 : (0xff5eu <= ch && ch <= 0xff9fu) // Halfwidth and Fullwidth Forms(a part)
3023 : ) {
3024 : return true;
3025 : }
3026 : char16_t ch2;
3027 0 : if (NS_IS_HIGH_SURROGATE(ch) && aFrag->GetLength() > uint32_t(aPos) + 1 &&
3028 0 : NS_IS_LOW_SURROGATE(ch2 = aFrag->CharAt(aPos + 1))) {
3029 0 : uint32_t u = SURROGATE_TO_UCS4(ch, ch2);
3030 0 : if (0x20000u <= u && u <= 0x2ffffu) { // CJK Unified Ideographs Extension B,
3031 : // CJK Unified Ideographs Extension C,
3032 : // CJK Unified Ideographs Extension D,
3033 : // CJK Compatibility Ideographs Supplement
3034 : return true;
3035 : }
3036 : }
3037 : }
3038 : return false;
3039 : }
3040 :
3041 : void
3042 0 : nsTextFrame::ClearMetrics(ReflowOutput& aMetrics)
3043 : {
3044 0 : aMetrics.ClearSize();
3045 0 : aMetrics.SetBlockStartAscent(0);
3046 0 : mAscent = 0;
3047 :
3048 0 : AddStateBits(TEXT_NO_RENDERED_GLYPHS);
3049 0 : }
3050 :
3051 0 : static int32_t FindChar(const nsTextFragment* frag,
3052 : int32_t aOffset, int32_t aLength, char16_t ch)
3053 : {
3054 0 : int32_t i = 0;
3055 0 : if (frag->Is2b()) {
3056 0 : const char16_t* str = frag->Get2b() + aOffset;
3057 0 : for (; i < aLength; ++i) {
3058 0 : if (*str == ch)
3059 0 : return i + aOffset;
3060 0 : ++str;
3061 : }
3062 : } else {
3063 0 : if (uint16_t(ch) <= 0xFF) {
3064 0 : const char* str = frag->Get1b() + aOffset;
3065 0 : const void* p = memchr(str, ch, aLength);
3066 0 : if (p)
3067 0 : return (static_cast<const char*>(p) - str) + aOffset;
3068 : }
3069 : }
3070 : return -1;
3071 : }
3072 :
3073 0 : static bool IsChineseOrJapanese(const nsTextFrame* aFrame)
3074 : {
3075 0 : if (aFrame->ShouldSuppressLineBreak()) {
3076 : // Always treat ruby as CJ language so that those characters can
3077 : // be expanded properly even when surrounded by other language.
3078 : return true;
3079 : }
3080 :
3081 0 : nsAtom* language = aFrame->StyleFont()->mLanguage;
3082 0 : if (!language) {
3083 : return false;
3084 : }
3085 0 : return nsStyleUtil::MatchesLanguagePrefix(language, u"ja") ||
3086 0 : nsStyleUtil::MatchesLanguagePrefix(language, u"zh");
3087 : }
3088 :
3089 : #ifdef DEBUG
3090 0 : static bool IsInBounds(const gfxSkipCharsIterator& aStart, int32_t aContentLength,
3091 : gfxTextRun::Range aRange) {
3092 0 : if (aStart.GetSkippedOffset() > aRange.start)
3093 : return false;
3094 0 : if (aContentLength == INT32_MAX)
3095 : return true;
3096 0 : gfxSkipCharsIterator iter(aStart);
3097 0 : iter.AdvanceOriginal(aContentLength);
3098 0 : return iter.GetSkippedOffset() >= aRange.end;
3099 : }
3100 : #endif
3101 :
3102 0 : class MOZ_STACK_CLASS PropertyProvider final : public gfxTextRun::PropertyProvider {
3103 : typedef gfxTextRun::Range Range;
3104 : typedef gfxTextRun::HyphenType HyphenType;
3105 :
3106 : public:
3107 : /**
3108 : * Use this constructor for reflow, when we don't know what text is
3109 : * really mapped by the frame and we have a lot of other data around.
3110 : *
3111 : * @param aLength can be INT32_MAX to indicate we cover all the text
3112 : * associated with aFrame up to where its flow chain ends in the given
3113 : * textrun. If INT32_MAX is passed, justification and hyphen-related methods
3114 : * cannot be called, nor can GetOriginalLength().
3115 : */
3116 0 : PropertyProvider(gfxTextRun* aTextRun, const nsStyleText* aTextStyle,
3117 : const nsTextFragment* aFrag, nsTextFrame* aFrame,
3118 : const gfxSkipCharsIterator& aStart, int32_t aLength,
3119 : nsIFrame* aLineContainer,
3120 : nscoord aOffsetFromBlockOriginForTabs,
3121 : nsTextFrame::TextRunType aWhichTextRun)
3122 0 : : mTextRun(aTextRun), mFontGroup(nullptr),
3123 : mTextStyle(aTextStyle), mFrag(aFrag),
3124 : mLineContainer(aLineContainer),
3125 : mFrame(aFrame), mStart(aStart), mTempIterator(aStart),
3126 : mTabWidths(nullptr), mTabWidthsAnalyzedLimit(0),
3127 : mLength(aLength),
3128 0 : mWordSpacing(WordSpacing(aFrame, mTextRun, aTextStyle)),
3129 0 : mLetterSpacing(LetterSpacing(aFrame, aTextStyle)),
3130 : mHyphenWidth(-1),
3131 : mOffsetFromBlockOriginForTabs(aOffsetFromBlockOriginForTabs),
3132 : mReflowing(true),
3133 0 : mWhichTextRun(aWhichTextRun)
3134 : {
3135 0 : NS_ASSERTION(mStart.IsInitialized(), "Start not initialized?");
3136 0 : }
3137 :
3138 : /**
3139 : * Use this constructor after the frame has been reflowed and we don't
3140 : * have other data around. Gets everything from the frame. EnsureTextRun
3141 : * *must* be called before this!!!
3142 : */
3143 0 : PropertyProvider(nsTextFrame* aFrame, const gfxSkipCharsIterator& aStart,
3144 : nsTextFrame::TextRunType aWhichTextRun)
3145 0 : : mTextRun(aFrame->GetTextRun(aWhichTextRun)), mFontGroup(nullptr),
3146 0 : mTextStyle(aFrame->StyleText()),
3147 0 : mFrag(aFrame->GetContent()->GetText()),
3148 : mLineContainer(nullptr),
3149 : mFrame(aFrame), mStart(aStart), mTempIterator(aStart),
3150 : mTabWidths(nullptr), mTabWidthsAnalyzedLimit(0),
3151 0 : mLength(aFrame->GetContentLength()),
3152 0 : mWordSpacing(WordSpacing(aFrame, mTextRun)),
3153 0 : mLetterSpacing(LetterSpacing(aFrame)),
3154 : mHyphenWidth(-1),
3155 : mOffsetFromBlockOriginForTabs(0),
3156 : mReflowing(false),
3157 0 : mWhichTextRun(aWhichTextRun)
3158 : {
3159 0 : NS_ASSERTION(mTextRun, "Textrun not initialized!");
3160 0 : }
3161 :
3162 : // Call this after construction if you're not going to reflow the text
3163 : void InitializeForDisplay(bool aTrimAfter);
3164 :
3165 : void InitializeForMeasure();
3166 :
3167 : void GetSpacing(Range aRange, Spacing* aSpacing) const override;
3168 : gfxFloat GetHyphenWidth() const override;
3169 : void GetHyphenationBreaks(Range aRange, HyphenType* aBreakBefore) const override;
3170 0 : StyleHyphens GetHyphensOption() const override {
3171 0 : return mTextStyle->mHyphens;
3172 : }
3173 :
3174 0 : already_AddRefed<DrawTarget> GetDrawTarget() const override {
3175 0 : return CreateReferenceDrawTarget(GetFrame());
3176 : }
3177 :
3178 0 : uint32_t GetAppUnitsPerDevUnit() const override {
3179 0 : return mTextRun->GetAppUnitsPerDevUnit();
3180 : }
3181 :
3182 : void GetSpacingInternal(Range aRange, Spacing* aSpacing, bool aIgnoreTabs) const;
3183 :
3184 : /**
3185 : * Compute the justification information in given DOM range, return
3186 : * justification info and assignments if requested.
3187 : */
3188 : JustificationInfo ComputeJustification(
3189 : Range aRange, nsTArray<JustificationAssignment>* aAssignments = nullptr);
3190 :
3191 : const nsTextFrame* GetFrame() const { return mFrame; }
3192 : // This may not be equal to the frame offset/length in because we may have
3193 : // adjusted for whitespace trimming according to the state bits set in the frame
3194 : // (for the static provider)
3195 0 : const gfxSkipCharsIterator& GetStart() const { return mStart; }
3196 : // May return INT32_MAX if that was given to the constructor
3197 0 : uint32_t GetOriginalLength() const {
3198 0 : NS_ASSERTION(mLength != INT32_MAX, "Length not known");
3199 0 : return mLength;
3200 : }
3201 : const nsTextFragment* GetFragment() const { return mFrag; }
3202 :
3203 0 : gfxFontGroup* GetFontGroup() const {
3204 0 : if (!mFontGroup) {
3205 0 : InitFontGroupAndFontMetrics();
3206 : }
3207 0 : return mFontGroup;
3208 : }
3209 :
3210 0 : nsFontMetrics* GetFontMetrics() const {
3211 0 : if (!mFontMetrics) {
3212 0 : InitFontGroupAndFontMetrics();
3213 : }
3214 0 : return mFontMetrics;
3215 : }
3216 :
3217 : void CalcTabWidths(Range aTransformedRange, gfxFloat aTabWidth) const;
3218 :
3219 : const gfxSkipCharsIterator& GetEndHint() const { return mTempIterator; }
3220 :
3221 : protected:
3222 : void SetupJustificationSpacing(bool aPostReflow);
3223 :
3224 0 : void InitFontGroupAndFontMetrics() const {
3225 0 : float inflation = (mWhichTextRun == nsTextFrame::eInflated)
3226 0 : ? mFrame->GetFontSizeInflation() : 1.0f;
3227 0 : mFontGroup = GetFontGroupForFrame(mFrame, inflation,
3228 0 : getter_AddRefs(mFontMetrics));
3229 0 : }
3230 :
3231 : const RefPtr<gfxTextRun> mTextRun;
3232 : mutable gfxFontGroup* mFontGroup;
3233 : mutable RefPtr<nsFontMetrics> mFontMetrics;
3234 : const nsStyleText* mTextStyle;
3235 : const nsTextFragment* mFrag;
3236 : const nsIFrame* mLineContainer;
3237 : nsTextFrame* mFrame;
3238 : gfxSkipCharsIterator mStart; // Offset in original and transformed string
3239 : const gfxSkipCharsIterator mTempIterator;
3240 :
3241 : // Either null, or pointing to the frame's TabWidthProperty.
3242 : mutable TabWidthStore* mTabWidths;
3243 : // How far we've done tab-width calculation; this is ONLY valid when
3244 : // mTabWidths is nullptr (otherwise rely on mTabWidths->mLimit instead).
3245 : // It's a DOM offset relative to the current frame's offset.
3246 : mutable uint32_t mTabWidthsAnalyzedLimit;
3247 :
3248 : int32_t mLength; // DOM string length, may be INT32_MAX
3249 : const gfxFloat mWordSpacing; // space for each whitespace char
3250 : const gfxFloat mLetterSpacing; // space for each letter
3251 : mutable gfxFloat mHyphenWidth;
3252 : mutable gfxFloat mOffsetFromBlockOriginForTabs;
3253 :
3254 : // The values in mJustificationSpacings corresponds to unskipped
3255 : // characters start from mJustificationArrayStart.
3256 : uint32_t mJustificationArrayStart;
3257 : nsTArray<Spacing> mJustificationSpacings;
3258 :
3259 : const bool mReflowing;
3260 : const nsTextFrame::TextRunType mWhichTextRun;
3261 : };
3262 :
3263 : /**
3264 : * Finds the offset of the first character of the cluster containing aPos
3265 : */
3266 0 : static void FindClusterStart(const gfxTextRun* aTextRun,
3267 : int32_t aOriginalStart,
3268 : gfxSkipCharsIterator* aPos)
3269 : {
3270 0 : while (aPos->GetOriginalOffset() > aOriginalStart) {
3271 0 : if (aPos->IsOriginalCharSkipped() ||
3272 0 : aTextRun->IsClusterStart(aPos->GetSkippedOffset())) {
3273 : break;
3274 : }
3275 0 : aPos->AdvanceOriginal(-1);
3276 : }
3277 0 : }
3278 :
3279 : /**
3280 : * Finds the offset of the last character of the cluster containing aPos.
3281 : * If aAllowSplitLigature is false, we also check for a ligature-group
3282 : * start.
3283 : */
3284 0 : static void FindClusterEnd(const gfxTextRun* aTextRun,
3285 : int32_t aOriginalEnd,
3286 : gfxSkipCharsIterator* aPos,
3287 : bool aAllowSplitLigature = true)
3288 : {
3289 0 : MOZ_ASSERT(aPos->GetOriginalOffset() < aOriginalEnd,
3290 : "character outside string");
3291 :
3292 0 : aPos->AdvanceOriginal(1);
3293 0 : while (aPos->GetOriginalOffset() < aOriginalEnd) {
3294 0 : if (aPos->IsOriginalCharSkipped() ||
3295 0 : (aTextRun->IsClusterStart(aPos->GetSkippedOffset()) &&
3296 0 : (aAllowSplitLigature ||
3297 0 : aTextRun->IsLigatureGroupStart(aPos->GetSkippedOffset())))) {
3298 : break;
3299 : }
3300 0 : aPos->AdvanceOriginal(1);
3301 : }
3302 0 : aPos->AdvanceOriginal(-1);
3303 0 : }
3304 :
3305 : JustificationInfo
3306 0 : PropertyProvider::ComputeJustification(
3307 : Range aRange, nsTArray<JustificationAssignment>* aAssignments)
3308 : {
3309 0 : JustificationInfo info;
3310 :
3311 : // Horizontal-in-vertical frame is orthogonal to the line, so it
3312 : // doesn't actually include any justification opportunity inside.
3313 : // The spec says such frame should be treated as a U+FFFC. Since we
3314 : // do not insert justification opportunities on the sides of that
3315 : // character, the sides of this frame are not justifiable either.
3316 0 : if (mFrame->Style()->IsTextCombined()) {
3317 0 : return info;
3318 : }
3319 :
3320 0 : bool isCJ = IsChineseOrJapanese(mFrame);
3321 : nsSkipCharsRunIterator run(
3322 0 : mStart, nsSkipCharsRunIterator::LENGTH_INCLUDES_SKIPPED, aRange.Length());
3323 0 : run.SetOriginalOffset(aRange.start);
3324 0 : mJustificationArrayStart = run.GetSkippedOffset();
3325 :
3326 0 : nsTArray<JustificationAssignment> assignments;
3327 0 : assignments.SetCapacity(aRange.Length());
3328 0 : while (run.NextRun()) {
3329 0 : uint32_t originalOffset = run.GetOriginalOffset();
3330 0 : uint32_t skippedOffset = run.GetSkippedOffset();
3331 0 : uint32_t length = run.GetRunLength();
3332 0 : assignments.SetLength(skippedOffset + length - mJustificationArrayStart);
3333 :
3334 0 : gfxSkipCharsIterator iter = run.GetPos();
3335 0 : for (uint32_t i = 0; i < length; ++i) {
3336 0 : uint32_t offset = originalOffset + i;
3337 0 : if (!IsJustifiableCharacter(mTextStyle, mFrag, offset, isCJ)) {
3338 : continue;
3339 : }
3340 :
3341 0 : iter.SetOriginalOffset(offset);
3342 :
3343 0 : FindClusterStart(mTextRun, originalOffset, &iter);
3344 0 : uint32_t firstCharOffset = iter.GetSkippedOffset();
3345 0 : uint32_t firstChar = firstCharOffset > mJustificationArrayStart ?
3346 0 : firstCharOffset - mJustificationArrayStart : 0;
3347 0 : if (!firstChar) {
3348 : info.mIsStartJustifiable = true;
3349 : } else {
3350 0 : auto& assign = assignments[firstChar];
3351 0 : auto& prevAssign = assignments[firstChar - 1];
3352 0 : if (prevAssign.mGapsAtEnd) {
3353 0 : prevAssign.mGapsAtEnd = 1;
3354 0 : assign.mGapsAtStart = 1;
3355 : } else {
3356 0 : assign.mGapsAtStart = 2;
3357 0 : info.mInnerOpportunities++;
3358 : }
3359 : }
3360 :
3361 0 : FindClusterEnd(mTextRun, originalOffset + length, &iter);
3362 0 : uint32_t lastChar = iter.GetSkippedOffset() - mJustificationArrayStart;
3363 : // Assign the two gaps temporary to the last char. If the next cluster is
3364 : // justifiable as well, one of the gaps will be removed by code above.
3365 0 : assignments[lastChar].mGapsAtEnd = 2;
3366 0 : info.mInnerOpportunities++;
3367 :
3368 : // Skip the whole cluster
3369 0 : i = iter.GetOriginalOffset() - originalOffset;
3370 : }
3371 : }
3372 :
3373 0 : if (!assignments.IsEmpty() && assignments.LastElement().mGapsAtEnd) {
3374 : // We counted the expansion opportunity after the last character,
3375 : // but it is not an inner opportunity.
3376 0 : MOZ_ASSERT(info.mInnerOpportunities > 0);
3377 0 : info.mInnerOpportunities--;
3378 0 : info.mIsEndJustifiable = true;
3379 : }
3380 :
3381 0 : if (aAssignments) {
3382 0 : *aAssignments = std::move(assignments);
3383 : }
3384 0 : return info;
3385 : }
3386 :
3387 : // aStart, aLength in transformed string offsets
3388 : void
3389 0 : PropertyProvider::GetSpacing(Range aRange, Spacing* aSpacing) const
3390 : {
3391 0 : GetSpacingInternal(aRange, aSpacing,
3392 0 : !(mTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_HAS_TAB));
3393 0 : }
3394 :
3395 : static bool
3396 0 : CanAddSpacingAfter(const gfxTextRun* aTextRun, uint32_t aOffset)
3397 : {
3398 0 : if (aOffset + 1 >= aTextRun->GetLength())
3399 : return true;
3400 0 : return aTextRun->IsClusterStart(aOffset + 1) &&
3401 0 : aTextRun->IsLigatureGroupStart(aOffset + 1) &&
3402 0 : !aTextRun->CharIsFormattingControl(aOffset);
3403 : }
3404 :
3405 : static gfxFloat
3406 0 : ComputeTabWidthAppUnits(const nsIFrame* aFrame, gfxTextRun* aTextRun)
3407 : {
3408 0 : const nsStyleText* textStyle = aFrame->StyleText();
3409 0 : if (textStyle->mTabSize.GetUnit() != eStyleUnit_Factor) {
3410 0 : nscoord w = textStyle->mTabSize.GetCoordValue();
3411 0 : MOZ_ASSERT(w >= 0);
3412 0 : return w;
3413 : }
3414 :
3415 0 : gfxFloat spaces = textStyle->mTabSize.GetFactorValue();
3416 0 : MOZ_ASSERT(spaces >= 0);
3417 :
3418 : // Round the space width when converting to appunits the same way
3419 : // textruns do.
3420 : gfxFloat spaceWidthAppUnits =
3421 0 : NS_round(GetFirstFontMetrics(aTextRun->GetFontGroup(),
3422 0 : aTextRun->IsVertical()).spaceWidth *
3423 0 : aTextRun->GetAppUnitsPerDevUnit());
3424 0 : return spaces * spaceWidthAppUnits;
3425 : }
3426 :
3427 : void
3428 0 : PropertyProvider::GetSpacingInternal(Range aRange, Spacing* aSpacing,
3429 : bool aIgnoreTabs) const
3430 : {
3431 0 : MOZ_ASSERT(IsInBounds(mStart, mLength, aRange), "Range out of bounds");
3432 :
3433 : uint32_t index;
3434 0 : for (index = 0; index < aRange.Length(); ++index) {
3435 0 : aSpacing[index].mBefore = 0.0;
3436 0 : aSpacing[index].mAfter = 0.0;
3437 : }
3438 :
3439 0 : if (mFrame->Style()->IsTextCombined()) {
3440 0 : return;
3441 : }
3442 :
3443 : // Find our offset into the original+transformed string
3444 0 : gfxSkipCharsIterator start(mStart);
3445 0 : start.SetSkippedOffset(aRange.start);
3446 :
3447 : // First, compute the word and letter spacing
3448 0 : if (mWordSpacing || mLetterSpacing) {
3449 : // Iterate over non-skipped characters
3450 : nsSkipCharsRunIterator run(
3451 0 : start, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aRange.Length());
3452 0 : while (run.NextRun()) {
3453 0 : uint32_t runOffsetInSubstring = run.GetSkippedOffset() - aRange.start;
3454 0 : gfxSkipCharsIterator iter = run.GetPos();
3455 0 : for (int32_t i = 0; i < run.GetRunLength(); ++i) {
3456 0 : if (CanAddSpacingAfter(mTextRun, run.GetSkippedOffset() + i)) {
3457 : // End of a cluster, not in a ligature: put letter-spacing after it
3458 0 : aSpacing[runOffsetInSubstring + i].mAfter += mLetterSpacing;
3459 : }
3460 0 : if (IsCSSWordSpacingSpace(mFrag, i + run.GetOriginalOffset(),
3461 0 : mFrame, mTextStyle)) {
3462 : // It kinda sucks, but space characters can be part of clusters,
3463 : // and even still be whitespace (I think!)
3464 0 : iter.SetSkippedOffset(run.GetSkippedOffset() + i);
3465 0 : FindClusterEnd(mTextRun, run.GetOriginalOffset() + run.GetRunLength(),
3466 0 : &iter);
3467 0 : uint32_t runOffset = iter.GetSkippedOffset() - aRange.start;
3468 0 : aSpacing[runOffset].mAfter += mWordSpacing;
3469 : }
3470 : }
3471 : }
3472 : }
3473 :
3474 : // Now add tab spacing, if there is any
3475 0 : if (!aIgnoreTabs) {
3476 0 : gfxFloat tabWidth = ComputeTabWidthAppUnits(mFrame, mTextRun);
3477 0 : if (tabWidth > 0) {
3478 0 : CalcTabWidths(aRange, tabWidth);
3479 0 : if (mTabWidths) {
3480 0 : mTabWidths->ApplySpacing(aSpacing,
3481 0 : aRange.start - mStart.GetSkippedOffset(),
3482 0 : aRange.Length());
3483 : }
3484 : }
3485 : }
3486 :
3487 : // Now add in justification spacing
3488 0 : if (mJustificationSpacings.Length() > 0) {
3489 : // If there is any spaces trimmed at the end, aStart + aLength may
3490 : // be larger than the flags array. When that happens, we can simply
3491 : // ignore those spaces.
3492 0 : auto arrayEnd = mJustificationArrayStart +
3493 0 : static_cast<uint32_t>(mJustificationSpacings.Length());
3494 0 : auto end = std::min(aRange.end, arrayEnd);
3495 0 : MOZ_ASSERT(aRange.start >= mJustificationArrayStart);
3496 0 : for (auto i = aRange.start; i < end; i++) {
3497 : const auto& spacing =
3498 0 : mJustificationSpacings[i - mJustificationArrayStart];
3499 0 : uint32_t offset = i - aRange.start;
3500 0 : aSpacing[offset].mBefore += spacing.mBefore;
3501 0 : aSpacing[offset].mAfter += spacing.mAfter;
3502 : }
3503 : }
3504 : }
3505 :
3506 : // aX and the result are in whole appunits.
3507 : static gfxFloat
3508 0 : AdvanceToNextTab(gfxFloat aX, gfxFloat aTabWidth)
3509 : {
3510 :
3511 : // Advance aX to the next multiple of *aCachedTabWidth. We must advance
3512 : // by at least 1 appunit.
3513 : // XXX should we make this 1 CSS pixel?
3514 0 : return ceil((aX + 1) / aTabWidth) * aTabWidth;
3515 : }
3516 :
3517 : void
3518 0 : PropertyProvider::CalcTabWidths(Range aRange, gfxFloat aTabWidth) const
3519 : {
3520 0 : MOZ_ASSERT(aTabWidth > 0);
3521 :
3522 0 : if (!mTabWidths) {
3523 0 : if (mReflowing && !mLineContainer) {
3524 : // Intrinsic width computation does its own tab processing. We
3525 : // just don't do anything here.
3526 : return;
3527 : }
3528 0 : if (!mReflowing) {
3529 0 : mTabWidths = mFrame->GetProperty(TabWidthProperty());
3530 : #ifdef DEBUG
3531 : // If we're not reflowing, we should have already computed the
3532 : // tab widths; check that they're available as far as the last
3533 : // tab character present (if any)
3534 0 : for (uint32_t i = aRange.end; i > aRange.start; --i) {
3535 0 : if (mTextRun->CharIsTab(i - 1)) {
3536 0 : uint32_t startOffset = mStart.GetSkippedOffset();
3537 0 : NS_ASSERTION(mTabWidths && mTabWidths->mLimit + startOffset >= i,
3538 : "Precomputed tab widths are missing!");
3539 : break;
3540 : }
3541 : }
3542 : #endif
3543 : return;
3544 : }
3545 : }
3546 :
3547 0 : uint32_t startOffset = mStart.GetSkippedOffset();
3548 0 : MOZ_ASSERT(aRange.start >= startOffset, "wrong start offset");
3549 0 : MOZ_ASSERT(aRange.end <= startOffset + mLength, "beyond the end");
3550 : uint32_t tabsEnd =
3551 0 : (mTabWidths ? mTabWidths->mLimit : mTabWidthsAnalyzedLimit) + startOffset;
3552 0 : if (tabsEnd < aRange.end) {
3553 0 : NS_ASSERTION(mReflowing,
3554 : "We need precomputed tab widths, but don't have enough.");
3555 :
3556 0 : for (uint32_t i = tabsEnd; i < aRange.end; ++i) {
3557 : Spacing spacing;
3558 0 : GetSpacingInternal(Range(i, i + 1), &spacing, true);
3559 0 : mOffsetFromBlockOriginForTabs += spacing.mBefore;
3560 :
3561 0 : if (!mTextRun->CharIsTab(i)) {
3562 0 : if (mTextRun->IsClusterStart(i)) {
3563 : uint32_t clusterEnd = i + 1;
3564 0 : while (clusterEnd < mTextRun->GetLength() &&
3565 0 : !mTextRun->IsClusterStart(clusterEnd)) {
3566 0 : ++clusterEnd;
3567 : }
3568 0 : mOffsetFromBlockOriginForTabs +=
3569 0 : mTextRun->GetAdvanceWidth(Range(i, clusterEnd), nullptr);
3570 : }
3571 : } else {
3572 0 : if (!mTabWidths) {
3573 0 : mTabWidths = new TabWidthStore(mFrame->GetContentOffset());
3574 0 : mFrame->SetProperty(TabWidthProperty(), mTabWidths);
3575 : }
3576 0 : double nextTab = AdvanceToNextTab(mOffsetFromBlockOriginForTabs,
3577 0 : aTabWidth);
3578 0 : mTabWidths->mWidths.AppendElement(TabWidth(i - startOffset,
3579 0 : NSToIntRound(nextTab - mOffsetFromBlockOriginForTabs)));
3580 0 : mOffsetFromBlockOriginForTabs = nextTab;
3581 : }
3582 :
3583 0 : mOffsetFromBlockOriginForTabs += spacing.mAfter;
3584 : }
3585 :
3586 0 : if (mTabWidths) {
3587 0 : mTabWidths->mLimit = aRange.end - startOffset;
3588 : }
3589 : }
3590 :
3591 0 : if (!mTabWidths) {
3592 : // Delete any stale property that may be left on the frame
3593 0 : mFrame->DeleteProperty(TabWidthProperty());
3594 0 : mTabWidthsAnalyzedLimit = std::max(mTabWidthsAnalyzedLimit,
3595 0 : aRange.end - startOffset);
3596 : }
3597 : }
3598 :
3599 : gfxFloat
3600 0 : PropertyProvider::GetHyphenWidth() const
3601 : {
3602 0 : if (mHyphenWidth < 0) {
3603 0 : mHyphenWidth = GetFontGroup()->GetHyphenWidth(this);
3604 : }
3605 0 : return mHyphenWidth + mLetterSpacing;
3606 : }
3607 :
3608 : static inline bool
3609 0 : IS_HYPHEN(char16_t u)
3610 : {
3611 0 : return (u == char16_t('-') ||
3612 0 : u == 0x058A || // ARMENIAN HYPHEN
3613 0 : u == 0x2010 || // HYPHEN
3614 0 : u == 0x2012 || // FIGURE DASH
3615 0 : u == 0x2013); // EN DASH
3616 : }
3617 :
3618 : void
3619 0 : PropertyProvider::GetHyphenationBreaks(Range aRange, HyphenType* aBreakBefore) const
3620 : {
3621 0 : MOZ_ASSERT(IsInBounds(mStart, mLength, aRange), "Range out of bounds");
3622 0 : MOZ_ASSERT(mLength != INT32_MAX, "Can't call this with undefined length");
3623 :
3624 0 : if (!mTextStyle->WhiteSpaceCanWrap(mFrame) ||
3625 0 : mTextStyle->mHyphens == StyleHyphens::None)
3626 : {
3627 0 : memset(aBreakBefore, static_cast<uint8_t>(HyphenType::None),
3628 0 : aRange.Length() * sizeof(HyphenType));
3629 0 : return;
3630 : }
3631 :
3632 : // Iterate through the original-string character runs
3633 : nsSkipCharsRunIterator run(
3634 0 : mStart, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aRange.Length());
3635 0 : run.SetSkippedOffset(aRange.start);
3636 : // We need to visit skipped characters so that we can detect SHY
3637 0 : run.SetVisitSkipped();
3638 :
3639 0 : int32_t prevTrailingCharOffset = run.GetPos().GetOriginalOffset() - 1;
3640 : bool allowHyphenBreakBeforeNextChar =
3641 0 : prevTrailingCharOffset >= mStart.GetOriginalOffset() &&
3642 0 : prevTrailingCharOffset < mStart.GetOriginalOffset() + mLength &&
3643 0 : mFrag->CharAt(prevTrailingCharOffset) == CH_SHY;
3644 :
3645 0 : while (run.NextRun()) {
3646 0 : NS_ASSERTION(run.GetRunLength() > 0, "Shouldn't return zero-length runs");
3647 0 : if (run.IsSkipped()) {
3648 : // Check if there's a soft hyphen which would let us hyphenate before
3649 : // the next non-skipped character. Don't look at soft hyphens followed
3650 : // by other skipped characters, we won't use them.
3651 0 : allowHyphenBreakBeforeNextChar =
3652 0 : mFrag->CharAt(run.GetOriginalOffset() + run.GetRunLength() - 1) == CH_SHY;
3653 : } else {
3654 0 : int32_t runOffsetInSubstring = run.GetSkippedOffset() - aRange.start;
3655 0 : memset(aBreakBefore + runOffsetInSubstring,
3656 : static_cast<uint8_t>(HyphenType::None),
3657 0 : run.GetRunLength() * sizeof(HyphenType));
3658 : // Don't allow hyphen breaks at the start of the line
3659 0 : aBreakBefore[runOffsetInSubstring] =
3660 0 : allowHyphenBreakBeforeNextChar &&
3661 0 : (!(mFrame->GetStateBits() & TEXT_START_OF_LINE) ||
3662 0 : run.GetSkippedOffset() > mStart.GetSkippedOffset())
3663 0 : ? HyphenType::Soft
3664 : : HyphenType::None;
3665 0 : allowHyphenBreakBeforeNextChar = false;
3666 : }
3667 : }
3668 :
3669 0 : if (mTextStyle->mHyphens == StyleHyphens::Auto) {
3670 0 : uint32_t currentFragOffset = mStart.GetOriginalOffset();
3671 0 : for (uint32_t i = 0; i < aRange.Length(); ++i) {
3672 0 : if (IS_HYPHEN(mFrag->CharAt(currentFragOffset + i))) {
3673 0 : aBreakBefore[i] = HyphenType::Explicit;
3674 0 : continue;
3675 : }
3676 :
3677 0 : if (mTextRun->CanHyphenateBefore(aRange.start + i) &&
3678 0 : aBreakBefore[i] == HyphenType::None) {
3679 0 : aBreakBefore[i] = HyphenType::AutoWithoutManualInSameWord;
3680 : }
3681 : }
3682 : }
3683 : }
3684 :
3685 : void
3686 0 : PropertyProvider::InitializeForDisplay(bool aTrimAfter)
3687 : {
3688 : nsTextFrame::TrimmedOffsets trimmed =
3689 0 : mFrame->GetTrimmedOffsets(mFrag, aTrimAfter);
3690 0 : mStart.SetOriginalOffset(trimmed.mStart);
3691 0 : mLength = trimmed.mLength;
3692 0 : SetupJustificationSpacing(true);
3693 0 : }
3694 :
3695 : void
3696 0 : PropertyProvider::InitializeForMeasure()
3697 : {
3698 : nsTextFrame::TrimmedOffsets trimmed =
3699 0 : mFrame->GetTrimmedOffsets(mFrag, true, false);
3700 0 : mStart.SetOriginalOffset(trimmed.mStart);
3701 0 : mLength = trimmed.mLength;
3702 0 : SetupJustificationSpacing(false);
3703 0 : }
3704 :
3705 :
3706 : void
3707 0 : PropertyProvider::SetupJustificationSpacing(bool aPostReflow)
3708 : {
3709 0 : MOZ_ASSERT(mLength != INT32_MAX, "Can't call this with undefined length");
3710 :
3711 0 : if (!(mFrame->GetStateBits() & TEXT_JUSTIFICATION_ENABLED)) {
3712 0 : return;
3713 : }
3714 :
3715 0 : gfxSkipCharsIterator start(mStart), end(mStart);
3716 : // We can't just use our mLength here; when InitializeForDisplay is
3717 : // called with false for aTrimAfter, we still shouldn't be assigning
3718 : // justification space to any trailing whitespace.
3719 : nsTextFrame::TrimmedOffsets trimmed =
3720 0 : mFrame->GetTrimmedOffsets(mFrag, true, aPostReflow);
3721 0 : end.AdvanceOriginal(trimmed.mLength);
3722 0 : gfxSkipCharsIterator realEnd(end);
3723 :
3724 0 : Range range(uint32_t(start.GetOriginalOffset()),
3725 0 : uint32_t(end.GetOriginalOffset()));
3726 0 : nsTArray<JustificationAssignment> assignments;
3727 0 : JustificationInfo info = ComputeJustification(range, &assignments);
3728 :
3729 0 : auto assign = mFrame->GetJustificationAssignment();
3730 0 : auto totalGaps = JustificationUtils::CountGaps(info, assign);
3731 0 : if (!totalGaps || assignments.IsEmpty()) {
3732 : // Nothing to do, nothing is justifiable and we shouldn't have any
3733 : // justification space assigned
3734 : return;
3735 : }
3736 :
3737 : // Remember that textrun measurements are in the run's orientation,
3738 : // so its advance "width" is actually a height in vertical writing modes,
3739 : // corresponding to the inline-direction of the frame.
3740 : gfxFloat naturalWidth =
3741 0 : mTextRun->GetAdvanceWidth(Range(mStart.GetSkippedOffset(),
3742 0 : realEnd.GetSkippedOffset()), this);
3743 0 : if (mFrame->GetStateBits() & TEXT_HYPHEN_BREAK) {
3744 0 : naturalWidth += GetHyphenWidth();
3745 : }
3746 0 : nscoord totalSpacing = mFrame->ISize() - naturalWidth;
3747 0 : if (totalSpacing <= 0) {
3748 : // No space available
3749 : return;
3750 : }
3751 :
3752 0 : assignments[0].mGapsAtStart = assign.mGapsAtStart;
3753 0 : assignments.LastElement().mGapsAtEnd = assign.mGapsAtEnd;
3754 :
3755 0 : MOZ_ASSERT(mJustificationSpacings.IsEmpty());
3756 0 : JustificationApplicationState state(totalGaps, totalSpacing);
3757 0 : mJustificationSpacings.SetCapacity(assignments.Length());
3758 0 : for (const JustificationAssignment& assign : assignments) {
3759 0 : Spacing* spacing = mJustificationSpacings.AppendElement();
3760 0 : spacing->mBefore = state.Consume(assign.mGapsAtStart);
3761 0 : spacing->mAfter = state.Consume(assign.mGapsAtEnd);
3762 : }
3763 : }
3764 :
3765 : //----------------------------------------------------------------------
3766 :
3767 : static nscolor
3768 0 : EnsureDifferentColors(nscolor colorA, nscolor colorB)
3769 : {
3770 0 : if (colorA == colorB) {
3771 : nscolor res;
3772 0 : res = NS_RGB(NS_GET_R(colorA) ^ 0xff,
3773 : NS_GET_G(colorA) ^ 0xff,
3774 : NS_GET_B(colorA) ^ 0xff);
3775 0 : return res;
3776 : }
3777 : return colorA;
3778 : }
3779 :
3780 : //-----------------------------------------------------------------------------
3781 :
3782 0 : nsTextPaintStyle::nsTextPaintStyle(nsTextFrame* aFrame)
3783 : : mFrame(aFrame),
3784 0 : mPresContext(aFrame->PresContext()),
3785 : mInitCommonColors(false),
3786 : mInitSelectionColorsAndShadow(false),
3787 : mResolveColors(true),
3788 0 : mHasSelectionShadow(false)
3789 : {
3790 0 : for (uint32_t i = 0; i < ArrayLength(mSelectionStyle); i++)
3791 0 : mSelectionStyle[i].mInit = false;
3792 0 : }
3793 :
3794 : bool
3795 0 : nsTextPaintStyle::EnsureSufficientContrast(nscolor *aForeColor, nscolor *aBackColor)
3796 : {
3797 0 : InitCommonColors();
3798 :
3799 : // If the combination of selection background color and frame background color
3800 : // is sufficient contrast, don't exchange the selection colors.
3801 : int32_t backLuminosityDifference =
3802 0 : NS_LUMINOSITY_DIFFERENCE(*aBackColor, mFrameBackgroundColor);
3803 0 : if (backLuminosityDifference >= mSufficientContrast)
3804 : return false;
3805 :
3806 : // Otherwise, we should use the higher-contrast color for the selection
3807 : // background color.
3808 : int32_t foreLuminosityDifference =
3809 0 : NS_LUMINOSITY_DIFFERENCE(*aForeColor, mFrameBackgroundColor);
3810 0 : if (backLuminosityDifference < foreLuminosityDifference) {
3811 0 : nscolor tmpColor = *aForeColor;
3812 0 : *aForeColor = *aBackColor;
3813 0 : *aBackColor = tmpColor;
3814 0 : return true;
3815 : }
3816 : return false;
3817 : }
3818 :
3819 : nscolor
3820 0 : nsTextPaintStyle::GetTextColor()
3821 : {
3822 0 : if (nsSVGUtils::IsInSVGTextSubtree(mFrame)) {
3823 0 : if (!mResolveColors)
3824 : return NS_SAME_AS_FOREGROUND_COLOR;
3825 :
3826 0 : const nsStyleSVG* style = mFrame->StyleSVG();
3827 0 : switch (style->mFill.Type()) {
3828 : case eStyleSVGPaintType_None:
3829 : return NS_RGBA(0, 0, 0, 0);
3830 : case eStyleSVGPaintType_Color:
3831 0 : return nsLayoutUtils::GetColor(mFrame, &nsStyleSVG::mFill);
3832 : default:
3833 0 : NS_ERROR("cannot resolve SVG paint to nscolor");
3834 0 : return NS_RGBA(0, 0, 0, 255);
3835 : }
3836 : }
3837 :
3838 0 : return nsLayoutUtils::GetColor(mFrame, &nsStyleText::mWebkitTextFillColor);
3839 : }
3840 :
3841 : bool
3842 0 : nsTextPaintStyle::GetSelectionColors(nscolor* aForeColor,
3843 : nscolor* aBackColor)
3844 : {
3845 0 : NS_ASSERTION(aForeColor, "aForeColor is null");
3846 0 : NS_ASSERTION(aBackColor, "aBackColor is null");
3847 :
3848 0 : if (!InitSelectionColorsAndShadow())
3849 : return false;
3850 :
3851 0 : *aForeColor = mSelectionTextColor;
3852 0 : *aBackColor = mSelectionBGColor;
3853 0 : return true;
3854 : }
3855 :
3856 : void
3857 0 : nsTextPaintStyle::GetHighlightColors(nscolor* aForeColor,
3858 : nscolor* aBackColor)
3859 : {
3860 0 : NS_ASSERTION(aForeColor, "aForeColor is null");
3861 0 : NS_ASSERTION(aBackColor, "aBackColor is null");
3862 :
3863 0 : const nsFrameSelection* frameSelection = mFrame->GetConstFrameSelection();
3864 : const Selection* selection =
3865 0 : frameSelection->GetSelection(SelectionType::eFind);
3866 0 : const SelectionCustomColors* customColors = nullptr;
3867 0 : if (selection) {
3868 0 : customColors = selection->GetCustomColors();
3869 : }
3870 :
3871 0 : if (!customColors) {
3872 : nscolor backColor =
3873 0 : LookAndFeel::GetColor(LookAndFeel::eColorID_TextHighlightBackground);
3874 : nscolor foreColor =
3875 0 : LookAndFeel::GetColor(LookAndFeel::eColorID_TextHighlightForeground);
3876 0 : EnsureSufficientContrast(&foreColor, &backColor);
3877 0 : *aForeColor = foreColor;
3878 0 : *aBackColor = backColor;
3879 :
3880 : return;
3881 : }
3882 :
3883 0 : if (customColors->mForegroundColor && customColors->mBackgroundColor) {
3884 0 : nscolor foreColor = *customColors->mForegroundColor;
3885 0 : nscolor backColor = *customColors->mBackgroundColor;
3886 :
3887 0 : if (EnsureSufficientContrast(&foreColor, &backColor) &&
3888 0 : customColors->mAltForegroundColor &&
3889 0 : customColors->mAltBackgroundColor) {
3890 0 : foreColor = *customColors->mAltForegroundColor;
3891 0 : backColor = *customColors->mAltBackgroundColor;
3892 : }
3893 :
3894 0 : *aForeColor = foreColor;
3895 0 : *aBackColor = backColor;
3896 : return;
3897 : }
3898 :
3899 0 : InitCommonColors();
3900 :
3901 0 : if (customColors->mBackgroundColor) {
3902 : // !mForegroundColor means "currentColor"; the current color of the text.
3903 0 : nscolor foreColor = GetTextColor();
3904 0 : nscolor backColor = *customColors->mBackgroundColor;
3905 :
3906 : int32_t luminosityDifference =
3907 0 : NS_LUMINOSITY_DIFFERENCE(foreColor, backColor);
3908 :
3909 0 : if (mSufficientContrast > luminosityDifference &&
3910 0 : customColors->mAltBackgroundColor) {
3911 : int32_t altLuminosityDifference =
3912 0 : NS_LUMINOSITY_DIFFERENCE(foreColor, *customColors->mAltBackgroundColor);
3913 :
3914 0 : if (luminosityDifference < altLuminosityDifference) {
3915 0 : backColor = *customColors->mAltBackgroundColor;
3916 : }
3917 : }
3918 :
3919 0 : *aForeColor = foreColor;
3920 0 : *aBackColor = backColor;
3921 0 : return;
3922 : }
3923 :
3924 0 : if (customColors->mForegroundColor) {
3925 0 : nscolor foreColor = *customColors->mForegroundColor;
3926 : // !mBackgroundColor means "transparent"; the current color of the background.
3927 :
3928 : int32_t luminosityDifference =
3929 0 : NS_LUMINOSITY_DIFFERENCE(foreColor, mFrameBackgroundColor);
3930 :
3931 0 : if (mSufficientContrast > luminosityDifference &&
3932 0 : customColors->mAltForegroundColor) {
3933 : int32_t altLuminosityDifference =
3934 0 : NS_LUMINOSITY_DIFFERENCE(*customColors->mForegroundColor, mFrameBackgroundColor);
3935 :
3936 0 : if (luminosityDifference < altLuminosityDifference) {
3937 0 : foreColor = *customColors->mAltForegroundColor;
3938 : }
3939 : }
3940 :
3941 0 : *aForeColor = foreColor;
3942 0 : *aBackColor = NS_TRANSPARENT;
3943 0 : return;
3944 : }
3945 :
3946 : // There are neither mForegroundColor nor mBackgroundColor.
3947 0 : *aForeColor = GetTextColor();
3948 0 : *aBackColor = NS_TRANSPARENT;
3949 : }
3950 :
3951 : void
3952 0 : nsTextPaintStyle::GetURLSecondaryColor(nscolor* aForeColor)
3953 : {
3954 0 : NS_ASSERTION(aForeColor, "aForeColor is null");
3955 :
3956 0 : nscolor textColor = GetTextColor();
3957 0 : textColor = NS_RGBA(NS_GET_R(textColor),
3958 : NS_GET_G(textColor),
3959 : NS_GET_B(textColor),
3960 : (uint8_t)(255 * 0.5f));
3961 : // Don't use true alpha color for readability.
3962 0 : InitCommonColors();
3963 0 : *aForeColor = NS_ComposeColors(mFrameBackgroundColor, textColor);
3964 0 : }
3965 :
3966 : void
3967 0 : nsTextPaintStyle::GetIMESelectionColors(int32_t aIndex,
3968 : nscolor* aForeColor,
3969 : nscolor* aBackColor)
3970 : {
3971 0 : NS_ASSERTION(aForeColor, "aForeColor is null");
3972 0 : NS_ASSERTION(aBackColor, "aBackColor is null");
3973 0 : NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range");
3974 :
3975 0 : nsSelectionStyle* selectionStyle = GetSelectionStyle(aIndex);
3976 0 : *aForeColor = selectionStyle->mTextColor;
3977 0 : *aBackColor = selectionStyle->mBGColor;
3978 0 : }
3979 :
3980 : bool
3981 0 : nsTextPaintStyle::GetSelectionUnderlineForPaint(int32_t aIndex,
3982 : nscolor* aLineColor,
3983 : float* aRelativeSize,
3984 : uint8_t* aStyle)
3985 : {
3986 0 : NS_ASSERTION(aLineColor, "aLineColor is null");
3987 0 : NS_ASSERTION(aRelativeSize, "aRelativeSize is null");
3988 0 : NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range");
3989 :
3990 0 : nsSelectionStyle* selectionStyle = GetSelectionStyle(aIndex);
3991 0 : if (selectionStyle->mUnderlineStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE ||
3992 0 : selectionStyle->mUnderlineColor == NS_TRANSPARENT ||
3993 0 : selectionStyle->mUnderlineRelativeSize <= 0.0f)
3994 : return false;
3995 :
3996 0 : *aLineColor = selectionStyle->mUnderlineColor;
3997 0 : *aRelativeSize = selectionStyle->mUnderlineRelativeSize;
3998 0 : *aStyle = selectionStyle->mUnderlineStyle;
3999 0 : return true;
4000 : }
4001 :
4002 : void
4003 0 : nsTextPaintStyle::InitCommonColors()
4004 : {
4005 0 : if (mInitCommonColors)
4006 : return;
4007 :
4008 : nsIFrame* bgFrame =
4009 0 : nsCSSRendering::FindNonTransparentBackgroundFrame(mFrame);
4010 0 : NS_ASSERTION(bgFrame, "Cannot find NonTransparentBackgroundFrame.");
4011 : nscolor bgColor = bgFrame->
4012 0 : GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor);
4013 :
4014 0 : nscolor defaultBgColor = mPresContext->DefaultBackgroundColor();
4015 0 : mFrameBackgroundColor = NS_ComposeColors(defaultBgColor, bgColor);
4016 :
4017 0 : mSystemFieldForegroundColor =
4018 0 : LookAndFeel::GetColor(LookAndFeel::eColorID__moz_fieldtext);
4019 0 : mSystemFieldBackgroundColor =
4020 0 : LookAndFeel::GetColor(LookAndFeel::eColorID__moz_field);
4021 :
4022 0 : if (bgFrame->IsThemed()) {
4023 : // Assume a native widget has sufficient contrast always
4024 0 : mSufficientContrast = 0;
4025 0 : mInitCommonColors = true;
4026 0 : return;
4027 : }
4028 :
4029 0 : NS_ASSERTION(NS_GET_A(defaultBgColor) == 255,
4030 : "default background color is not opaque");
4031 :
4032 : nscolor defaultWindowBackgroundColor =
4033 0 : LookAndFeel::GetColor(LookAndFeel::eColorID_WindowBackground);
4034 : nscolor selectionTextColor =
4035 0 : LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectForeground);
4036 : nscolor selectionBGColor =
4037 0 : LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectBackground);
4038 :
4039 0 : mSufficientContrast =
4040 0 : std::min(std::min(NS_SUFFICIENT_LUMINOSITY_DIFFERENCE,
4041 0 : NS_LUMINOSITY_DIFFERENCE(selectionTextColor,
4042 : selectionBGColor)),
4043 0 : NS_LUMINOSITY_DIFFERENCE(defaultWindowBackgroundColor,
4044 0 : selectionBGColor));
4045 :
4046 0 : mInitCommonColors = true;
4047 : }
4048 :
4049 : nscolor
4050 0 : nsTextPaintStyle::GetSystemFieldForegroundColor()
4051 : {
4052 0 : InitCommonColors();
4053 0 : return mSystemFieldForegroundColor;
4054 : }
4055 :
4056 : nscolor
4057 0 : nsTextPaintStyle::GetSystemFieldBackgroundColor()
4058 : {
4059 0 : InitCommonColors();
4060 0 : return mSystemFieldBackgroundColor;
4061 : }
4062 :
4063 : bool
4064 0 : nsTextPaintStyle::InitSelectionColorsAndShadow()
4065 : {
4066 0 : if (mInitSelectionColorsAndShadow)
4067 : return true;
4068 :
4069 : int16_t selectionFlags;
4070 0 : int16_t selectionStatus = mFrame->GetSelectionStatus(&selectionFlags);
4071 0 : if (!(selectionFlags & nsISelectionDisplay::DISPLAY_TEXT) ||
4072 : selectionStatus < nsISelectionController::SELECTION_ON) {
4073 : // Not displaying the normal selection.
4074 : // We're not caching this fact, so every call to GetSelectionColors
4075 : // will come through here. We could avoid this, but it's not really worth it.
4076 : return false;
4077 : }
4078 :
4079 0 : mInitSelectionColorsAndShadow = true;
4080 :
4081 0 : if (selectionStatus == nsISelectionController::SELECTION_ON) {
4082 : // Use ::selection pseudo class.
4083 0 : if (RefPtr<ComputedStyle> style = mFrame->ComputeSelectionStyle()) {
4084 0 : mSelectionBGColor =
4085 0 : style->GetVisitedDependentColor(&nsStyleBackground::mBackgroundColor);
4086 0 : mSelectionTextColor =
4087 0 : style->GetVisitedDependentColor(&nsStyleText::mWebkitTextFillColor);
4088 0 : mHasSelectionShadow = true;
4089 0 : mSelectionShadow = style->StyleText()->mTextShadow;
4090 0 : return true;
4091 : }
4092 : }
4093 :
4094 : nscolor selectionBGColor =
4095 0 : LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectBackground);
4096 :
4097 0 : if (selectionStatus == nsISelectionController::SELECTION_ATTENTION) {
4098 : mSelectionBGColor =
4099 0 : LookAndFeel::GetColor(
4100 : LookAndFeel::eColorID_TextSelectBackgroundAttention);
4101 0 : mSelectionBGColor = EnsureDifferentColors(mSelectionBGColor,
4102 : selectionBGColor);
4103 0 : } else if (selectionStatus != nsISelectionController::SELECTION_ON) {
4104 : mSelectionBGColor =
4105 0 : LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectBackgroundDisabled);
4106 0 : mSelectionBGColor = EnsureDifferentColors(mSelectionBGColor,
4107 : selectionBGColor);
4108 : } else {
4109 0 : mSelectionBGColor = selectionBGColor;
4110 : }
4111 :
4112 0 : mSelectionTextColor =
4113 0 : LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectForeground);
4114 :
4115 0 : if (mResolveColors) {
4116 : // On MacOS X, we don't exchange text color and BG color.
4117 0 : if (mSelectionTextColor == NS_DONT_CHANGE_COLOR) {
4118 0 : nscolor frameColor = nsSVGUtils::IsInSVGTextSubtree(mFrame)
4119 0 : ? mFrame->GetVisitedDependentColor(&nsStyleSVG::mFill)
4120 0 : : mFrame->GetVisitedDependentColor(&nsStyleText::mWebkitTextFillColor);
4121 0 : mSelectionTextColor = EnsureDifferentColors(frameColor, mSelectionBGColor);
4122 0 : } else if (mSelectionTextColor == NS_CHANGE_COLOR_IF_SAME_AS_BG) {
4123 0 : nscolor frameColor = nsSVGUtils::IsInSVGTextSubtree(mFrame)
4124 0 : ? mFrame->GetVisitedDependentColor(&nsStyleSVG::mFill)
4125 0 : : mFrame->GetVisitedDependentColor(&nsStyleText::mWebkitTextFillColor);
4126 0 : if (frameColor == mSelectionBGColor) {
4127 0 : mSelectionTextColor =
4128 0 : LookAndFeel::GetColor(LookAndFeel::eColorID_TextSelectForegroundCustom);
4129 : }
4130 : } else {
4131 0 : EnsureSufficientContrast(&mSelectionTextColor, &mSelectionBGColor);
4132 : }
4133 : } else {
4134 0 : if (mSelectionTextColor == NS_DONT_CHANGE_COLOR) {
4135 0 : mSelectionTextColor = NS_SAME_AS_FOREGROUND_COLOR;
4136 : }
4137 : }
4138 : return true;
4139 : }
4140 :
4141 : nsTextPaintStyle::nsSelectionStyle*
4142 0 : nsTextPaintStyle::GetSelectionStyle(int32_t aIndex)
4143 : {
4144 0 : InitSelectionStyle(aIndex);
4145 0 : return &mSelectionStyle[aIndex];
4146 : }
4147 :
4148 : struct StyleIDs {
4149 : LookAndFeel::ColorID mForeground, mBackground, mLine;
4150 : LookAndFeel::IntID mLineStyle;
4151 : LookAndFeel::FloatID mLineRelativeSize;
4152 : };
4153 : static StyleIDs SelectionStyleIDs[] = {
4154 : { LookAndFeel::eColorID_IMERawInputForeground,
4155 : LookAndFeel::eColorID_IMERawInputBackground,
4156 : LookAndFeel::eColorID_IMERawInputUnderline,
4157 : LookAndFeel::eIntID_IMERawInputUnderlineStyle,
4158 : LookAndFeel::eFloatID_IMEUnderlineRelativeSize },
4159 : { LookAndFeel::eColorID_IMESelectedRawTextForeground,
4160 : LookAndFeel::eColorID_IMESelectedRawTextBackground,
4161 : LookAndFeel::eColorID_IMESelectedRawTextUnderline,
4162 : LookAndFeel::eIntID_IMESelectedRawTextUnderlineStyle,
4163 : LookAndFeel::eFloatID_IMEUnderlineRelativeSize },
4164 : { LookAndFeel::eColorID_IMEConvertedTextForeground,
4165 : LookAndFeel::eColorID_IMEConvertedTextBackground,
4166 : LookAndFeel::eColorID_IMEConvertedTextUnderline,
4167 : LookAndFeel::eIntID_IMEConvertedTextUnderlineStyle,
4168 : LookAndFeel::eFloatID_IMEUnderlineRelativeSize },
4169 : { LookAndFeel::eColorID_IMESelectedConvertedTextForeground,
4170 : LookAndFeel::eColorID_IMESelectedConvertedTextBackground,
4171 : LookAndFeel::eColorID_IMESelectedConvertedTextUnderline,
4172 : LookAndFeel::eIntID_IMESelectedConvertedTextUnderline,
4173 : LookAndFeel::eFloatID_IMEUnderlineRelativeSize },
4174 : { LookAndFeel::eColorID_LAST_COLOR,
4175 : LookAndFeel::eColorID_LAST_COLOR,
4176 : LookAndFeel::eColorID_SpellCheckerUnderline,
4177 : LookAndFeel::eIntID_SpellCheckerUnderlineStyle,
4178 : LookAndFeel::eFloatID_SpellCheckerUnderlineRelativeSize }
4179 : };
4180 :
4181 : void
4182 0 : nsTextPaintStyle::InitSelectionStyle(int32_t aIndex)
4183 : {
4184 0 : NS_ASSERTION(aIndex >= 0 && aIndex < 5, "aIndex is invalid");
4185 0 : nsSelectionStyle* selectionStyle = &mSelectionStyle[aIndex];
4186 0 : if (selectionStyle->mInit)
4187 0 : return;
4188 :
4189 0 : StyleIDs* styleIDs = &SelectionStyleIDs[aIndex];
4190 :
4191 : nscolor foreColor, backColor;
4192 0 : if (styleIDs->mForeground == LookAndFeel::eColorID_LAST_COLOR) {
4193 0 : foreColor = NS_SAME_AS_FOREGROUND_COLOR;
4194 : } else {
4195 0 : foreColor = LookAndFeel::GetColor(styleIDs->mForeground);
4196 : }
4197 0 : if (styleIDs->mBackground == LookAndFeel::eColorID_LAST_COLOR) {
4198 0 : backColor = NS_TRANSPARENT;
4199 : } else {
4200 0 : backColor = LookAndFeel::GetColor(styleIDs->mBackground);
4201 : }
4202 :
4203 : // Convert special color to actual color
4204 0 : NS_ASSERTION(foreColor != NS_TRANSPARENT,
4205 : "foreColor cannot be NS_TRANSPARENT");
4206 0 : NS_ASSERTION(backColor != NS_SAME_AS_FOREGROUND_COLOR,
4207 : "backColor cannot be NS_SAME_AS_FOREGROUND_COLOR");
4208 0 : NS_ASSERTION(backColor != NS_40PERCENT_FOREGROUND_COLOR,
4209 : "backColor cannot be NS_40PERCENT_FOREGROUND_COLOR");
4210 :
4211 0 : if (mResolveColors) {
4212 0 : foreColor = GetResolvedForeColor(foreColor, GetTextColor(), backColor);
4213 :
4214 0 : if (NS_GET_A(backColor) > 0)
4215 0 : EnsureSufficientContrast(&foreColor, &backColor);
4216 : }
4217 :
4218 : nscolor lineColor;
4219 : float relativeSize;
4220 : uint8_t lineStyle;
4221 0 : GetSelectionUnderline(mPresContext, aIndex,
4222 0 : &lineColor, &relativeSize, &lineStyle);
4223 :
4224 0 : if (mResolveColors)
4225 0 : lineColor = GetResolvedForeColor(lineColor, foreColor, backColor);
4226 :
4227 0 : selectionStyle->mTextColor = foreColor;
4228 0 : selectionStyle->mBGColor = backColor;
4229 0 : selectionStyle->mUnderlineColor = lineColor;
4230 0 : selectionStyle->mUnderlineStyle = lineStyle;
4231 0 : selectionStyle->mUnderlineRelativeSize = relativeSize;
4232 0 : selectionStyle->mInit = true;
4233 : }
4234 :
4235 : /* static */ bool
4236 0 : nsTextPaintStyle::GetSelectionUnderline(nsPresContext* aPresContext,
4237 : int32_t aIndex,
4238 : nscolor* aLineColor,
4239 : float* aRelativeSize,
4240 : uint8_t* aStyle)
4241 : {
4242 0 : NS_ASSERTION(aPresContext, "aPresContext is null");
4243 0 : NS_ASSERTION(aRelativeSize, "aRelativeSize is null");
4244 0 : NS_ASSERTION(aStyle, "aStyle is null");
4245 0 : NS_ASSERTION(aIndex >= 0 && aIndex < 5, "Index out of range");
4246 :
4247 0 : StyleIDs& styleID = SelectionStyleIDs[aIndex];
4248 :
4249 0 : nscolor color = LookAndFeel::GetColor(styleID.mLine);
4250 0 : int32_t style = LookAndFeel::GetInt(styleID.mLineStyle);
4251 0 : if (style > NS_STYLE_TEXT_DECORATION_STYLE_MAX) {
4252 0 : NS_ERROR("Invalid underline style value is specified");
4253 0 : style = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
4254 : }
4255 0 : float size = LookAndFeel::GetFloat(styleID.mLineRelativeSize);
4256 :
4257 0 : NS_ASSERTION(size, "selection underline relative size must be larger than 0");
4258 :
4259 0 : if (aLineColor) {
4260 0 : *aLineColor = color;
4261 : }
4262 0 : *aRelativeSize = size;
4263 0 : *aStyle = style;
4264 :
4265 0 : return style != NS_STYLE_TEXT_DECORATION_STYLE_NONE &&
4266 0 : color != NS_TRANSPARENT &&
4267 0 : size > 0.0f;
4268 : }
4269 :
4270 : bool
4271 0 : nsTextPaintStyle::GetSelectionShadow(nsCSSShadowArray** aShadow)
4272 : {
4273 0 : if (!InitSelectionColorsAndShadow()) {
4274 : return false;
4275 : }
4276 :
4277 0 : if (mHasSelectionShadow) {
4278 0 : *aShadow = mSelectionShadow;
4279 0 : return true;
4280 : }
4281 :
4282 : return false;
4283 : }
4284 :
4285 0 : inline nscolor Get40PercentColor(nscolor aForeColor, nscolor aBackColor)
4286 : {
4287 0 : nscolor foreColor = NS_RGBA(NS_GET_R(aForeColor),
4288 : NS_GET_G(aForeColor),
4289 : NS_GET_B(aForeColor),
4290 : (uint8_t)(255 * 0.4f));
4291 : // Don't use true alpha color for readability.
4292 0 : return NS_ComposeColors(aBackColor, foreColor);
4293 : }
4294 :
4295 : nscolor
4296 0 : nsTextPaintStyle::GetResolvedForeColor(nscolor aColor,
4297 : nscolor aDefaultForeColor,
4298 : nscolor aBackColor)
4299 : {
4300 0 : if (aColor == NS_SAME_AS_FOREGROUND_COLOR)
4301 : return aDefaultForeColor;
4302 :
4303 0 : if (aColor != NS_40PERCENT_FOREGROUND_COLOR)
4304 : return aColor;
4305 :
4306 : // Get actual background color
4307 0 : nscolor actualBGColor = aBackColor;
4308 0 : if (actualBGColor == NS_TRANSPARENT) {
4309 0 : InitCommonColors();
4310 0 : actualBGColor = mFrameBackgroundColor;
4311 : }
4312 0 : return Get40PercentColor(aDefaultForeColor, actualBGColor);
4313 : }
4314 :
4315 : //-----------------------------------------------------------------------------
4316 :
4317 : #ifdef ACCESSIBILITY
4318 : a11y::AccType
4319 0 : nsTextFrame::AccessibleType()
4320 : {
4321 0 : if (IsEmpty()) {
4322 : RenderedText text = GetRenderedText(0,
4323 : UINT32_MAX, TextOffsetType::OFFSETS_IN_CONTENT_TEXT,
4324 0 : TrailingWhitespace::DONT_TRIM_TRAILING_WHITESPACE);
4325 0 : if (text.mString.IsEmpty()) {
4326 0 : return a11y::eNoType;
4327 : }
4328 : }
4329 :
4330 : return a11y::eTextLeafType;
4331 : }
4332 : #endif
4333 :
4334 :
4335 : //-----------------------------------------------------------------------------
4336 : void
4337 0 : nsTextFrame::Init(nsIContent* aContent,
4338 : nsContainerFrame* aParent,
4339 : nsIFrame* aPrevInFlow)
4340 : {
4341 0 : NS_ASSERTION(!aPrevInFlow, "Can't be a continuation!");
4342 0 : MOZ_ASSERT(aContent->IsText(), "Bogus content!");
4343 :
4344 : // Remove any NewlineOffsetProperty or InFlowContentLengthProperty since they
4345 : // might be invalid if the content was modified while there was no frame
4346 0 : if (aContent->HasFlag(NS_HAS_NEWLINE_PROPERTY)) {
4347 0 : aContent->DeleteProperty(nsGkAtoms::newline);
4348 0 : aContent->UnsetFlags(NS_HAS_NEWLINE_PROPERTY);
4349 : }
4350 0 : if (aContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) {
4351 0 : aContent->DeleteProperty(nsGkAtoms::flowlength);
4352 0 : aContent->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
4353 : }
4354 :
4355 : // Since our content has a frame now, this flag is no longer needed.
4356 0 : aContent->UnsetFlags(NS_CREATE_FRAME_IF_NON_WHITESPACE);
4357 :
4358 : // We're not a continuing frame.
4359 : // mContentOffset = 0; not necessary since we get zeroed out at init
4360 0 : nsFrame::Init(aContent, aParent, aPrevInFlow);
4361 0 : }
4362 :
4363 : void
4364 0 : nsTextFrame::ClearFrameOffsetCache()
4365 : {
4366 : // See if we need to remove ourselves from the offset cache
4367 0 : if (GetStateBits() & TEXT_IN_OFFSET_CACHE) {
4368 0 : nsIFrame* primaryFrame = mContent->GetPrimaryFrame();
4369 0 : if (primaryFrame) {
4370 : // The primary frame might be null here. For example, nsLineBox::DeleteLineList
4371 : // just destroys the frames in order, which means that the primary frame is already
4372 : // dead if we're a continuing text frame, in which case, all of its properties are
4373 : // gone, and we don't need to worry about deleting this property here.
4374 0 : primaryFrame->DeleteProperty(OffsetToFrameProperty());
4375 : }
4376 0 : RemoveStateBits(TEXT_IN_OFFSET_CACHE);
4377 : }
4378 0 : }
4379 :
4380 : void
4381 0 : nsTextFrame::DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData)
4382 : {
4383 0 : ClearFrameOffsetCache();
4384 :
4385 : // We might want to clear NS_CREATE_FRAME_IF_NON_WHITESPACE or
4386 : // NS_REFRAME_IF_WHITESPACE on mContent here, since our parent frame
4387 : // type might be changing. Not clear whether it's worth it.
4388 0 : ClearTextRuns();
4389 0 : if (mNextContinuation) {
4390 0 : mNextContinuation->SetPrevInFlow(nullptr);
4391 : }
4392 : // Let the base class destroy the frame
4393 0 : nsFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
4394 0 : }
4395 :
4396 0 : class nsContinuingTextFrame final : public nsTextFrame
4397 : {
4398 : public:
4399 0 : NS_DECL_FRAMEARENA_HELPERS(nsContinuingTextFrame)
4400 :
4401 : friend nsIFrame* NS_NewContinuingTextFrame(nsIPresShell* aPresShell, ComputedStyle* aStyle);
4402 :
4403 : void Init(nsIContent* aContent,
4404 : nsContainerFrame* aParent,
4405 : nsIFrame* aPrevInFlow) override;
4406 :
4407 : void DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData) override;
4408 :
4409 0 : nsTextFrame* GetPrevContinuation() const override
4410 : {
4411 0 : return mPrevContinuation;
4412 : }
4413 0 : void SetPrevContinuation(nsIFrame* aPrevContinuation) override
4414 : {
4415 0 : NS_ASSERTION(!aPrevContinuation || Type() == aPrevContinuation->Type(),
4416 : "setting a prev continuation with incorrect type!");
4417 0 : NS_ASSERTION (!nsSplittableFrame::IsInPrevContinuationChain(aPrevContinuation, this),
4418 : "creating a loop in continuation chain!");
4419 0 : mPrevContinuation = static_cast<nsTextFrame*>(aPrevContinuation);
4420 0 : RemoveStateBits(NS_FRAME_IS_FLUID_CONTINUATION);
4421 0 : }
4422 0 : nsIFrame* GetPrevInFlowVirtual() const override { return GetPrevInFlow(); }
4423 0 : nsTextFrame* GetPrevInFlow() const
4424 : {
4425 0 : return (GetStateBits() & NS_FRAME_IS_FLUID_CONTINUATION) ? mPrevContinuation : nullptr;
4426 : }
4427 0 : void SetPrevInFlow(nsIFrame* aPrevInFlow) override
4428 : {
4429 0 : NS_ASSERTION(!aPrevInFlow || Type() == aPrevInFlow->Type(),
4430 : "setting a prev in flow with incorrect type!");
4431 0 : NS_ASSERTION (!nsSplittableFrame::IsInPrevContinuationChain(aPrevInFlow, this),
4432 : "creating a loop in continuation chain!");
4433 0 : mPrevContinuation = static_cast<nsTextFrame*>(aPrevInFlow);
4434 0 : AddStateBits(NS_FRAME_IS_FLUID_CONTINUATION);
4435 0 : }
4436 : nsIFrame* FirstInFlow() const override;
4437 : nsIFrame* FirstContinuation() const override;
4438 :
4439 : void AddInlineMinISize(gfxContext* aRenderingContext,
4440 : InlineMinISizeData* aData) override;
4441 : void AddInlinePrefISize(gfxContext* aRenderingContext,
4442 : InlinePrefISizeData* aData) override;
4443 :
4444 : protected:
4445 0 : explicit nsContinuingTextFrame(ComputedStyle* aStyle)
4446 0 : : nsTextFrame(aStyle, kClassID)
4447 0 : {}
4448 :
4449 : nsTextFrame* mPrevContinuation;
4450 : };
4451 :
4452 : void
4453 0 : nsContinuingTextFrame::Init(nsIContent* aContent,
4454 : nsContainerFrame* aParent,
4455 : nsIFrame* aPrevInFlow)
4456 : {
4457 0 : NS_ASSERTION(aPrevInFlow, "Must be a continuation!");
4458 : // NOTE: bypassing nsTextFrame::Init!!!
4459 0 : nsFrame::Init(aContent, aParent, aPrevInFlow);
4460 :
4461 0 : nsTextFrame* prev = static_cast<nsTextFrame*>(aPrevInFlow);
4462 0 : nsTextFrame* nextContinuation = prev->GetNextContinuation();
4463 : // Hook the frame into the flow
4464 0 : SetPrevInFlow(aPrevInFlow);
4465 0 : aPrevInFlow->SetNextInFlow(this);
4466 0 : mContentOffset = prev->GetContentOffset() + prev->GetContentLengthHint();
4467 0 : NS_ASSERTION(mContentOffset < int32_t(aContent->GetText()->GetLength()),
4468 : "Creating ContinuingTextFrame, but there is no more content");
4469 0 : if (prev->Style() != Style()) {
4470 : // We're taking part of prev's text, and its style may be different
4471 : // so clear its textrun which may no longer be valid (and don't set ours)
4472 0 : prev->ClearTextRuns();
4473 : } else {
4474 0 : float inflation = prev->GetFontSizeInflation();
4475 0 : SetFontSizeInflation(inflation);
4476 0 : mTextRun = prev->GetTextRun(nsTextFrame::eInflated);
4477 0 : if (inflation != 1.0f) {
4478 : gfxTextRun *uninflatedTextRun =
4479 0 : prev->GetTextRun(nsTextFrame::eNotInflated);
4480 0 : if (uninflatedTextRun) {
4481 0 : SetTextRun(uninflatedTextRun, nsTextFrame::eNotInflated, 1.0f);
4482 : }
4483 : }
4484 : }
4485 0 : if (aPrevInFlow->GetStateBits() & NS_FRAME_IS_BIDI) {
4486 0 : FrameBidiData bidiData = aPrevInFlow->GetBidiData();
4487 0 : bidiData.precedingControl = kBidiLevelNone;
4488 0 : SetProperty(BidiDataProperty(), bidiData);
4489 :
4490 0 : if (nextContinuation) {
4491 0 : SetNextContinuation(nextContinuation);
4492 0 : nextContinuation->SetPrevContinuation(this);
4493 : // Adjust next-continuations' content offset as needed.
4494 0 : while (nextContinuation &&
4495 0 : nextContinuation->GetContentOffset() < mContentOffset) {
4496 : #ifdef DEBUG
4497 0 : FrameBidiData nextBidiData = nextContinuation->GetBidiData();
4498 0 : NS_ASSERTION(bidiData.embeddingLevel == nextBidiData.embeddingLevel &&
4499 : bidiData.baseLevel == nextBidiData.baseLevel,
4500 : "stealing text from different type of BIDI continuation");
4501 0 : MOZ_ASSERT(nextBidiData.precedingControl == kBidiLevelNone,
4502 : "There shouldn't be any virtual bidi formatting character "
4503 : "between continuations");
4504 : #endif
4505 0 : nextContinuation->mContentOffset = mContentOffset;
4506 0 : nextContinuation = nextContinuation->GetNextContinuation();
4507 : }
4508 : }
4509 0 : AddStateBits(NS_FRAME_IS_BIDI);
4510 : } // prev frame is bidi
4511 0 : }
4512 :
4513 : void
4514 0 : nsContinuingTextFrame::DestroyFrom(nsIFrame* aDestructRoot, PostDestroyData& aPostDestroyData)
4515 : {
4516 0 : ClearFrameOffsetCache();
4517 :
4518 : // The text associated with this frame will become associated with our
4519 : // prev-continuation. If that means the text has changed style, then
4520 : // we need to wipe out the text run for the text.
4521 : // Note that mPrevContinuation can be null if we're destroying the whole
4522 : // frame chain from the start to the end.
4523 : // If this frame is mentioned in the userData for a textrun (say
4524 : // because there's a direction change at the start of this frame), then
4525 : // we have to clear the textrun because we're going away and the
4526 : // textrun had better not keep a dangling reference to us.
4527 0 : if (IsInTextRunUserData() ||
4528 0 : (mPrevContinuation &&
4529 0 : mPrevContinuation->Style() != Style())) {
4530 0 : ClearTextRuns();
4531 : // Clear the previous continuation's text run also, so that it can rebuild
4532 : // the text run to include our text.
4533 0 : if (mPrevContinuation) {
4534 0 : mPrevContinuation->ClearTextRuns();
4535 : }
4536 : }
4537 0 : nsSplittableFrame::RemoveFromFlow(this);
4538 : // Let the base class destroy the frame
4539 0 : nsFrame::DestroyFrom(aDestructRoot, aPostDestroyData);
4540 0 : }
4541 :
4542 : nsIFrame*
4543 0 : nsContinuingTextFrame::FirstInFlow() const
4544 : {
4545 : // Can't cast to |nsContinuingTextFrame*| because the first one isn't.
4546 : nsIFrame *firstInFlow,
4547 : *previous = const_cast<nsIFrame*>
4548 0 : (static_cast<const nsIFrame*>(this));
4549 : do {
4550 0 : firstInFlow = previous;
4551 0 : previous = firstInFlow->GetPrevInFlow();
4552 0 : } while (previous);
4553 0 : MOZ_ASSERT(firstInFlow, "post-condition failed");
4554 0 : return firstInFlow;
4555 : }
4556 :
4557 : nsIFrame*
4558 0 : nsContinuingTextFrame::FirstContinuation() const
4559 : {
4560 : // Can't cast to |nsContinuingTextFrame*| because the first one isn't.
4561 : nsIFrame *firstContinuation,
4562 : *previous = const_cast<nsIFrame*>
4563 0 : (static_cast<const nsIFrame*>(mPrevContinuation));
4564 :
4565 0 : NS_ASSERTION(previous, "How can an nsContinuingTextFrame be the first continuation?");
4566 :
4567 : do {
4568 0 : firstContinuation = previous;
4569 0 : previous = firstContinuation->GetPrevContinuation();
4570 0 : } while (previous);
4571 0 : MOZ_ASSERT(firstContinuation, "post-condition failed");
4572 0 : return firstContinuation;
4573 : }
4574 :
4575 : // XXX Do we want to do all the work for the first-in-flow or do the
4576 : // work for each part? (Be careful of first-letter / first-line, though,
4577 : // especially first-line!) Doing all the work on the first-in-flow has
4578 : // the advantage of avoiding the potential for incremental reflow bugs,
4579 : // but depends on our maintining the frame tree in reasonable ways even
4580 : // for edge cases (block-within-inline splits, nextBidi, etc.)
4581 :
4582 : // XXX We really need to make :first-letter happen during frame
4583 : // construction.
4584 :
4585 : // Needed for text frames in XUL.
4586 : /* virtual */ nscoord
4587 0 : nsTextFrame::GetMinISize(gfxContext *aRenderingContext)
4588 : {
4589 0 : return nsLayoutUtils::MinISizeFromInline(this, aRenderingContext);
4590 : }
4591 :
4592 : // Needed for text frames in XUL.
4593 : /* virtual */ nscoord
4594 0 : nsTextFrame::GetPrefISize(gfxContext *aRenderingContext)
4595 : {
4596 0 : return nsLayoutUtils::PrefISizeFromInline(this, aRenderingContext);
4597 : }
4598 :
4599 : /* virtual */ void
4600 0 : nsContinuingTextFrame::AddInlineMinISize(gfxContext *aRenderingContext,
4601 : InlineMinISizeData *aData)
4602 : {
4603 : // Do nothing, since the first-in-flow accounts for everything.
4604 0 : }
4605 :
4606 : /* virtual */ void
4607 0 : nsContinuingTextFrame::AddInlinePrefISize(gfxContext *aRenderingContext,
4608 : InlinePrefISizeData *aData)
4609 : {
4610 : // Do nothing, since the first-in-flow accounts for everything.
4611 0 : }
4612 :
4613 : //----------------------------------------------------------------------
4614 :
4615 : #if defined(DEBUG_rbs) || defined(DEBUG_bzbarsky)
4616 : static void
4617 : VerifyNotDirty(nsFrameState state)
4618 : {
4619 : bool isZero = state & NS_FRAME_FIRST_REFLOW;
4620 : bool isDirty = state & NS_FRAME_IS_DIRTY;
4621 : if (!isZero && isDirty)
4622 : NS_WARNING("internal offsets may be out-of-sync");
4623 : }
4624 : #define DEBUG_VERIFY_NOT_DIRTY(state) \
4625 : VerifyNotDirty(state)
4626 : #else
4627 : #define DEBUG_VERIFY_NOT_DIRTY(state)
4628 : #endif
4629 :
4630 : nsIFrame*
4631 0 : NS_NewTextFrame(nsIPresShell* aPresShell, ComputedStyle* aStyle)
4632 : {
4633 0 : return new (aPresShell) nsTextFrame(aStyle);
4634 : }
4635 :
4636 0 : NS_IMPL_FRAMEARENA_HELPERS(nsTextFrame)
4637 :
4638 : nsIFrame*
4639 0 : NS_NewContinuingTextFrame(nsIPresShell* aPresShell, ComputedStyle* aStyle)
4640 : {
4641 0 : return new (aPresShell) nsContinuingTextFrame(aStyle);
4642 : }
4643 :
4644 0 : NS_IMPL_FRAMEARENA_HELPERS(nsContinuingTextFrame)
4645 :
4646 0 : nsTextFrame::~nsTextFrame()
4647 : {
4648 0 : }
4649 :
4650 : nsresult
4651 0 : nsTextFrame::GetCursor(const nsPoint& aPoint,
4652 : nsIFrame::Cursor& aCursor)
4653 : {
4654 0 : FillCursorInformationFromStyle(StyleUserInterface(), aCursor);
4655 0 : if (NS_STYLE_CURSOR_AUTO == aCursor.mCursor) {
4656 0 : if (!IsSelectable(nullptr)) {
4657 0 : aCursor.mCursor = NS_STYLE_CURSOR_DEFAULT;
4658 : } else {
4659 0 : aCursor.mCursor = GetWritingMode().IsVertical()
4660 0 : ? NS_STYLE_CURSOR_VERTICAL_TEXT : NS_STYLE_CURSOR_TEXT;
4661 : }
4662 : return NS_OK;
4663 : } else {
4664 0 : return nsFrame::GetCursor(aPoint, aCursor);
4665 : }
4666 : }
4667 :
4668 : nsTextFrame*
4669 0 : nsTextFrame::LastInFlow() const
4670 : {
4671 0 : nsTextFrame* lastInFlow = const_cast<nsTextFrame*>(this);
4672 0 : while (lastInFlow->GetNextInFlow()) {
4673 : lastInFlow = lastInFlow->GetNextInFlow();
4674 : }
4675 0 : MOZ_ASSERT(lastInFlow, "post-condition failed");
4676 0 : return lastInFlow;
4677 : }
4678 :
4679 : nsTextFrame*
4680 0 : nsTextFrame::LastContinuation() const
4681 : {
4682 0 : nsTextFrame* lastContinuation = const_cast<nsTextFrame*>(this);
4683 0 : while (lastContinuation->mNextContinuation) {
4684 : lastContinuation = lastContinuation->mNextContinuation;
4685 : }
4686 0 : MOZ_ASSERT(lastContinuation, "post-condition failed");
4687 0 : return lastContinuation;
4688 : }
4689 :
4690 : void
4691 0 : nsTextFrame::InvalidateFrame(uint32_t aDisplayItemKey, bool aRebuildDisplayItems)
4692 : {
4693 0 : if (nsSVGUtils::IsInSVGTextSubtree(this)) {
4694 : nsIFrame* svgTextFrame = nsLayoutUtils::GetClosestFrameOfType(
4695 0 : GetParent(), LayoutFrameType::SVGText);
4696 0 : svgTextFrame->InvalidateFrame();
4697 0 : return;
4698 : }
4699 0 : nsFrame::InvalidateFrame(aDisplayItemKey, aRebuildDisplayItems);
4700 : }
4701 :
4702 : void
4703 0 : nsTextFrame::InvalidateFrameWithRect(const nsRect& aRect, uint32_t aDisplayItemKey, bool aRebuildDisplayItems)
4704 : {
4705 0 : if (nsSVGUtils::IsInSVGTextSubtree(this)) {
4706 : nsIFrame* svgTextFrame = nsLayoutUtils::GetClosestFrameOfType(
4707 0 : GetParent(), LayoutFrameType::SVGText);
4708 0 : svgTextFrame->InvalidateFrame();
4709 0 : return;
4710 : }
4711 0 : nsFrame::InvalidateFrameWithRect(aRect, aDisplayItemKey, aRebuildDisplayItems);
4712 : }
4713 :
4714 : gfxTextRun*
4715 0 : nsTextFrame::GetUninflatedTextRun()
4716 : {
4717 0 : return GetProperty(UninflatedTextRunProperty());
4718 : }
4719 :
4720 : void
4721 0 : nsTextFrame::SetTextRun(gfxTextRun* aTextRun, TextRunType aWhichTextRun,
4722 : float aInflation)
4723 : {
4724 0 : NS_ASSERTION(aTextRun, "must have text run");
4725 :
4726 : // Our inflated text run is always stored in mTextRun. In the cases
4727 : // where our current inflation is not 1.0, however, we store two text
4728 : // runs, and the uninflated one goes in a frame property. We never
4729 : // store a single text run in both.
4730 0 : if (aWhichTextRun == eInflated) {
4731 0 : if (HasFontSizeInflation() && aInflation == 1.0f) {
4732 : // FIXME: Probably shouldn't do this within each SetTextRun
4733 : // method, but it doesn't hurt.
4734 0 : ClearTextRun(nullptr, nsTextFrame::eNotInflated);
4735 : }
4736 0 : SetFontSizeInflation(aInflation);
4737 : } else {
4738 0 : MOZ_ASSERT(aInflation == 1.0f, "unexpected inflation");
4739 0 : if (HasFontSizeInflation()) {
4740 : // Setting the property will not automatically increment the textrun's
4741 : // reference count, so we need to do it here.
4742 0 : aTextRun->AddRef();
4743 0 : SetProperty(UninflatedTextRunProperty(), aTextRun);
4744 0 : return;
4745 : }
4746 : // fall through to setting mTextRun
4747 : }
4748 :
4749 0 : mTextRun = aTextRun;
4750 :
4751 : // FIXME: Add assertions testing the relationship between
4752 : // GetFontSizeInflation() and whether we have an uninflated text run
4753 : // (but be aware that text runs can go away).
4754 : }
4755 :
4756 : bool
4757 0 : nsTextFrame::RemoveTextRun(gfxTextRun* aTextRun)
4758 : {
4759 0 : if (aTextRun == mTextRun) {
4760 0 : mTextRun = nullptr;
4761 0 : return true;
4762 : }
4763 0 : if ((GetStateBits() & TEXT_HAS_FONT_INFLATION) &&
4764 0 : GetProperty(UninflatedTextRunProperty()) == aTextRun) {
4765 0 : DeleteProperty(UninflatedTextRunProperty());
4766 0 : return true;
4767 : }
4768 : return false;
4769 : }
4770 :
4771 : void
4772 0 : nsTextFrame::ClearTextRun(nsTextFrame* aStartContinuation,
4773 : TextRunType aWhichTextRun)
4774 : {
4775 0 : RefPtr<gfxTextRun> textRun = GetTextRun(aWhichTextRun);
4776 0 : if (!textRun) {
4777 0 : return;
4778 : }
4779 :
4780 0 : DebugOnly<bool> checkmTextrun = textRun == mTextRun;
4781 0 : UnhookTextRunFromFrames(textRun, aStartContinuation);
4782 0 : MOZ_ASSERT(checkmTextrun ? !mTextRun
4783 : : !GetProperty(UninflatedTextRunProperty()));
4784 : }
4785 :
4786 : void
4787 0 : nsTextFrame::DisconnectTextRuns()
4788 : {
4789 0 : MOZ_ASSERT(!IsInTextRunUserData(),
4790 : "Textrun mentions this frame in its user data so we can't just disconnect");
4791 0 : mTextRun = nullptr;
4792 0 : if ((GetStateBits() & TEXT_HAS_FONT_INFLATION)) {
4793 0 : DeleteProperty(UninflatedTextRunProperty());
4794 : }
4795 0 : }
4796 :
4797 : nsresult
4798 0 : nsTextFrame::CharacterDataChanged(const CharacterDataChangeInfo& aInfo)
4799 : {
4800 0 : if (mContent->HasFlag(NS_HAS_NEWLINE_PROPERTY)) {
4801 0 : mContent->DeleteProperty(nsGkAtoms::newline);
4802 0 : mContent->UnsetFlags(NS_HAS_NEWLINE_PROPERTY);
4803 : }
4804 0 : if (mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) {
4805 0 : mContent->DeleteProperty(nsGkAtoms::flowlength);
4806 0 : mContent->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
4807 : }
4808 :
4809 : // Find the first frame whose text has changed. Frames that are entirely
4810 : // before the text change are completely unaffected.
4811 : nsTextFrame* next;
4812 : nsTextFrame* textFrame = this;
4813 : while (true) {
4814 0 : next = textFrame->GetNextContinuation();
4815 0 : if (!next || next->GetContentOffset() > int32_t(aInfo.mChangeStart))
4816 : break;
4817 : textFrame = next;
4818 : }
4819 :
4820 0 : int32_t endOfChangedText = aInfo.mChangeStart + aInfo.mReplaceLength;
4821 :
4822 : // Parent of the last frame that we passed to FrameNeedsReflow (or noticed
4823 : // had already received an earlier FrameNeedsReflow call).
4824 : // (For subsequent frames with this same parent, we can just set their
4825 : // dirty bit without bothering to call FrameNeedsReflow again.)
4826 0 : nsIFrame* lastDirtiedFrameParent = nullptr;
4827 :
4828 0 : nsIPresShell* shell = PresContext()->GetPresShell();
4829 0 : do {
4830 : // textFrame contained deleted text (or the insertion point,
4831 : // if this was a pure insertion).
4832 0 : textFrame->RemoveStateBits(TEXT_WHITESPACE_FLAGS);
4833 0 : textFrame->ClearTextRuns();
4834 :
4835 0 : nsIFrame* parentOfTextFrame = textFrame->GetParent();
4836 0 : bool areAncestorsAwareOfReflowRequest = false;
4837 0 : if (lastDirtiedFrameParent == parentOfTextFrame) {
4838 : // An earlier iteration of this loop already called
4839 : // FrameNeedsReflow for a sibling of |textFrame|.
4840 : areAncestorsAwareOfReflowRequest = true;
4841 : } else {
4842 0 : lastDirtiedFrameParent = parentOfTextFrame;
4843 : }
4844 :
4845 0 : if (textFrame->mReflowRequestedForCharDataChange) {
4846 : // We already requested a reflow for this frame; nothing to do.
4847 0 : MOZ_ASSERT(textFrame->HasAnyStateBits(NS_FRAME_IS_DIRTY),
4848 : "mReflowRequestedForCharDataChange should only be set "
4849 : "on dirty frames");
4850 : } else {
4851 : // Make sure textFrame is queued up for a reflow. Also set a flag so we
4852 : // don't waste time doing this again in repeated calls to this method.
4853 0 : textFrame->mReflowRequestedForCharDataChange = true;
4854 0 : if (!areAncestorsAwareOfReflowRequest) {
4855 : // Ask the parent frame to reflow me.
4856 : shell->FrameNeedsReflow(textFrame, nsIPresShell::eStyleChange,
4857 0 : NS_FRAME_IS_DIRTY);
4858 : } else {
4859 : // We already called FrameNeedsReflow on behalf of an earlier sibling,
4860 : // so we can just mark this frame as dirty and don't need to bother
4861 : // telling its ancestors.
4862 : // Note: if the parent is a block, we're cheating here because we should
4863 : // be marking our line dirty, but we're not. nsTextFrame::SetLength will
4864 : // do that when it gets called during reflow.
4865 0 : textFrame->AddStateBits(NS_FRAME_IS_DIRTY);
4866 : }
4867 : }
4868 0 : textFrame->InvalidateFrame();
4869 :
4870 : // Below, frames that start after the deleted text will be adjusted so that
4871 : // their offsets move with the trailing unchanged text. If this change
4872 : // deletes more text than it inserts, those frame offsets will decrease.
4873 : // We need to maintain the invariant that mContentOffset is non-decreasing
4874 : // along the continuation chain. So we need to ensure that frames that
4875 : // started in the deleted text are all still starting before the
4876 : // unchanged text.
4877 0 : if (textFrame->mContentOffset > endOfChangedText) {
4878 0 : textFrame->mContentOffset = endOfChangedText;
4879 : }
4880 :
4881 0 : textFrame = textFrame->GetNextContinuation();
4882 0 : } while (textFrame && textFrame->GetContentOffset() < int32_t(aInfo.mChangeEnd));
4883 :
4884 : // This is how much the length of the string changed by --- i.e.,
4885 : // how much the trailing unchanged text moved.
4886 : int32_t sizeChange =
4887 0 : aInfo.mChangeStart + aInfo.mReplaceLength - aInfo.mChangeEnd;
4888 :
4889 0 : if (sizeChange) {
4890 : // Fix the offsets of the text frames that start in the trailing
4891 : // unchanged text.
4892 0 : while (textFrame) {
4893 0 : textFrame->mContentOffset += sizeChange;
4894 : // XXX we could rescue some text runs by adjusting their user data
4895 : // to reflect the change in DOM offsets
4896 0 : textFrame->ClearTextRuns();
4897 0 : textFrame = textFrame->GetNextContinuation();
4898 : }
4899 : }
4900 :
4901 0 : return NS_OK;
4902 : }
4903 :
4904 : class nsDisplayText final : public nsCharClipDisplayItem {
4905 : public:
4906 : nsDisplayText(nsDisplayListBuilder* aBuilder, nsTextFrame* aFrame,
4907 : const Maybe<bool>& aIsSelected);
4908 : #ifdef NS_BUILD_REFCNT_LOGGING
4909 0 : virtual ~nsDisplayText() {
4910 0 : MOZ_COUNT_DTOR(nsDisplayText);
4911 0 : }
4912 : #endif
4913 :
4914 0 : virtual void RestoreState() override
4915 : {
4916 0 : nsCharClipDisplayItem::RestoreState();
4917 0 : mOpacity = 1.0f;
4918 0 : }
4919 :
4920 0 : virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder,
4921 : bool* aSnap) const override
4922 : {
4923 0 : *aSnap = false;
4924 0 : return mBounds;
4925 : }
4926 0 : virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
4927 : HitTestState* aState,
4928 : nsTArray<nsIFrame*> *aOutFrames) override {
4929 0 : if (nsRect(ToReferenceFrame(), mFrame->GetSize()).Intersects(aRect)) {
4930 0 : aOutFrames->AppendElement(mFrame);
4931 : }
4932 0 : }
4933 : virtual bool CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
4934 : mozilla::wr::IpcResourceUpdateQueue& aResources,
4935 : const StackingContextHelper& aSc,
4936 : WebRenderLayerManager* aManager,
4937 : nsDisplayListBuilder* aDisplayListBuilder) override;
4938 : virtual void Paint(nsDisplayListBuilder* aBuilder,
4939 : gfxContext* aCtx) override;
4940 0 : NS_DISPLAY_DECL_NAME("Text", TYPE_TEXT)
4941 :
4942 0 : virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) const override
4943 : {
4944 0 : if (gfxPlatform::GetPlatform()->RespectsFontStyleSmoothing()) {
4945 : // On OS X, web authors can turn off subpixel text rendering using the
4946 : // CSS property -moz-osx-font-smoothing. If they do that, we don't need
4947 : // to use component alpha layers for the affected text.
4948 0 : if (mFrame->StyleFont()->mFont.smoothing == NS_FONT_SMOOTHING_GRAYSCALE) {
4949 0 : return nsRect();
4950 : }
4951 : }
4952 : bool snap;
4953 : return GetBounds(aBuilder, &snap);
4954 : }
4955 :
4956 : virtual nsDisplayItemGeometry* AllocateGeometry(nsDisplayListBuilder* aBuilder) override;
4957 :
4958 : virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
4959 : const nsDisplayItemGeometry* aGeometry,
4960 : nsRegion *aInvalidRegion) const override;
4961 :
4962 : void RenderToContext(gfxContext* aCtx, nsDisplayListBuilder* aBuilder, bool aIsRecording = false);
4963 :
4964 0 : bool CanApplyOpacity() const override
4965 : {
4966 0 : nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
4967 0 : if (f->IsSelected()) {
4968 : return false;
4969 : }
4970 :
4971 0 : const nsStyleText* textStyle = f->StyleText();
4972 0 : if (textStyle->mTextShadow) {
4973 : return false;
4974 : }
4975 :
4976 0 : nsTextFrame::TextDecorations decorations;
4977 0 : f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors, decorations);
4978 0 : if (decorations.HasDecorationLines()) {
4979 : return false;
4980 : }
4981 :
4982 0 : return true;
4983 : }
4984 :
4985 0 : void ApplyOpacity(nsDisplayListBuilder* aBuilder,
4986 : float aOpacity,
4987 : const DisplayItemClipChain* aClip) override
4988 : {
4989 0 : NS_ASSERTION(CanApplyOpacity(), "ApplyOpacity should be allowed");
4990 0 : mOpacity = aOpacity;
4991 0 : IntersectClip(aBuilder, aClip, false);
4992 0 : }
4993 :
4994 0 : void WriteDebugInfo(std::stringstream& aStream) override
4995 : {
4996 : #ifdef DEBUG
4997 0 : aStream << " (\"";
4998 :
4999 0 : nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
5000 0 : nsCString buf;
5001 : int32_t totalContentLength;
5002 0 : f->ToCString(buf, &totalContentLength);
5003 :
5004 0 : aStream << buf.get() << "\")";
5005 : #endif
5006 0 : }
5007 :
5008 : nsRect mBounds;
5009 : float mOpacity;
5010 : };
5011 :
5012 0 : class nsDisplayTextGeometry : public nsCharClipGeometry
5013 : {
5014 : public:
5015 0 : nsDisplayTextGeometry(nsDisplayText* aItem, nsDisplayListBuilder* aBuilder)
5016 0 : : nsCharClipGeometry(aItem, aBuilder)
5017 0 : , mOpacity(aItem->mOpacity)
5018 : {
5019 0 : nsTextFrame* f = static_cast<nsTextFrame*>(aItem->Frame());
5020 0 : f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors, mDecorations);
5021 0 : }
5022 :
5023 : /**
5024 : * We store the computed text decorations here since they are
5025 : * computed using style data from parent frames. Any changes to these
5026 : * styles will only invalidate the parent frame and not this frame.
5027 : */
5028 : nsTextFrame::TextDecorations mDecorations;
5029 : float mOpacity;
5030 : };
5031 :
5032 : nsDisplayItemGeometry*
5033 0 : nsDisplayText::AllocateGeometry(nsDisplayListBuilder* aBuilder)
5034 : {
5035 0 : return new nsDisplayTextGeometry(this, aBuilder);
5036 : }
5037 :
5038 : void
5039 0 : nsDisplayText::ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
5040 : const nsDisplayItemGeometry* aGeometry,
5041 : nsRegion *aInvalidRegion) const
5042 : {
5043 0 : const nsDisplayTextGeometry* geometry = static_cast<const nsDisplayTextGeometry*>(aGeometry);
5044 0 : nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
5045 :
5046 0 : nsTextFrame::TextDecorations decorations;
5047 0 : f->GetTextDecorations(f->PresContext(), nsTextFrame::eResolvedColors, decorations);
5048 :
5049 : bool snap;
5050 0 : nsRect newRect = geometry->mBounds;
5051 0 : nsRect oldRect = GetBounds(aBuilder, &snap);
5052 0 : if (decorations != geometry->mDecorations ||
5053 0 : mVisIStartEdge != geometry->mVisIStartEdge ||
5054 0 : mVisIEndEdge != geometry->mVisIEndEdge ||
5055 0 : !oldRect.IsEqualInterior(newRect) ||
5056 0 : !geometry->mBorderRect.IsEqualInterior(GetBorderRect()) ||
5057 0 : mOpacity != geometry->mOpacity) {
5058 0 : aInvalidRegion->Or(oldRect, newRect);
5059 : }
5060 0 : }
5061 :
5062 : NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(TextCombineScaleFactorProperty, float)
5063 :
5064 : static float
5065 0 : GetTextCombineScaleFactor(nsTextFrame* aFrame)
5066 : {
5067 0 : float factor = aFrame->GetProperty(TextCombineScaleFactorProperty());
5068 0 : return factor ? factor : 1.0f;
5069 : }
5070 :
5071 0 : nsDisplayText::nsDisplayText(nsDisplayListBuilder* aBuilder, nsTextFrame* aFrame,
5072 0 : const Maybe<bool>& aIsSelected)
5073 : : nsCharClipDisplayItem(aBuilder, aFrame)
5074 0 : , mOpacity(1.0f)
5075 : {
5076 0 : MOZ_COUNT_CTOR(nsDisplayText);
5077 0 : mIsFrameSelected = aIsSelected;
5078 0 : mBounds = mFrame->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
5079 : // Bug 748228
5080 0 : mBounds.Inflate(mFrame->PresContext()->AppUnitsPerDevPixel());
5081 0 : }
5082 :
5083 : void
5084 0 : nsDisplayText::Paint(nsDisplayListBuilder* aBuilder,
5085 : gfxContext* aCtx) {
5086 0 : AUTO_PROFILER_LABEL("nsDisplayText::Paint", GRAPHICS);
5087 :
5088 : DrawTargetAutoDisableSubpixelAntialiasing disable(aCtx->GetDrawTarget(),
5089 0 : mDisableSubpixelAA);
5090 0 : RenderToContext(aCtx, aBuilder);
5091 0 : }
5092 :
5093 : bool
5094 0 : nsDisplayText::CreateWebRenderCommands(mozilla::wr::DisplayListBuilder& aBuilder,
5095 : mozilla::wr::IpcResourceUpdateQueue& aResources,
5096 : const StackingContextHelper& aSc,
5097 : WebRenderLayerManager* aManager,
5098 : nsDisplayListBuilder* aDisplayListBuilder)
5099 : {
5100 0 : if (mBounds.IsEmpty()) {
5101 : return true;
5102 : }
5103 :
5104 :
5105 0 : auto appUnitsPerDevPixel = Frame()->PresContext()->AppUnitsPerDevPixel();
5106 0 : gfx::Point deviceOffset = LayoutDevicePoint::FromAppUnits(
5107 0 : mBounds.TopLeft(), appUnitsPerDevPixel).ToUnknownPoint();
5108 :
5109 0 : RefPtr<TextDrawTarget> textDrawer = new TextDrawTarget(aBuilder, aSc, aManager, this, mBounds);
5110 0 : RefPtr<gfxContext> captureCtx = gfxContext::CreateOrNull(textDrawer, deviceOffset);
5111 :
5112 0 : RenderToContext(captureCtx, aDisplayListBuilder, true);
5113 :
5114 0 : return !textDrawer->HasUnsupportedFeatures();
5115 : }
5116 :
5117 : void
5118 0 : nsDisplayText::RenderToContext(gfxContext* aCtx, nsDisplayListBuilder* aBuilder, bool aIsRecording)
5119 : {
5120 0 : nsTextFrame* f = static_cast<nsTextFrame*>(mFrame);
5121 :
5122 : // Add 1 pixel of dirty area around mVisibleRect to allow us to paint
5123 : // antialiased pixels beyond the measured text extents.
5124 : // This is temporary until we do this in the actual calculation of text extents.
5125 0 : auto A2D = mFrame->PresContext()->AppUnitsPerDevPixel();
5126 : LayoutDeviceRect extraVisible =
5127 0 : LayoutDeviceRect::FromAppUnits(GetPaintRect(), A2D);
5128 0 : extraVisible.Inflate(1);
5129 :
5130 0 : gfxRect pixelVisible(extraVisible.x, extraVisible.y,
5131 0 : extraVisible.width, extraVisible.height);
5132 0 : pixelVisible.Inflate(2);
5133 0 : pixelVisible.RoundOut();
5134 :
5135 0 : bool willClip = !aBuilder->IsForGenerateGlyphMask() &&
5136 0 : !aBuilder->IsForPaintingSelectionBG() &&
5137 0 : !aIsRecording;
5138 0 : if (willClip) {
5139 0 : aCtx->NewPath();
5140 0 : aCtx->Rectangle(pixelVisible);
5141 0 : aCtx->Clip();
5142 : }
5143 :
5144 0 : NS_ASSERTION(mVisIStartEdge >= 0, "illegal start edge");
5145 0 : NS_ASSERTION(mVisIEndEdge >= 0, "illegal end edge");
5146 :
5147 0 : gfxContextMatrixAutoSaveRestore matrixSR;
5148 :
5149 0 : nsPoint framePt = ToReferenceFrame();
5150 0 : if (f->Style()->IsTextCombined()) {
5151 0 : float scaleFactor = GetTextCombineScaleFactor(f);
5152 0 : if (scaleFactor != 1.0f) {
5153 0 : if (auto* textDrawer = aCtx->GetTextDrawer()) {
5154 : // WebRender doesn't support scaling text like this yet
5155 0 : textDrawer->FoundUnsupportedFeature();
5156 0 : return;
5157 : }
5158 0 : matrixSR.SetContext(aCtx);
5159 : // Setup matrix to compress text for text-combine-upright if
5160 : // necessary. This is done here because we want selection be
5161 : // compressed at the same time as text.
5162 0 : gfxPoint pt = nsLayoutUtils::PointToGfxPoint(framePt, A2D);
5163 0 : gfxMatrix mat = aCtx->CurrentMatrixDouble()
5164 0 : .PreTranslate(pt).PreScale(scaleFactor, 1.0).PreTranslate(-pt);
5165 0 : aCtx->SetMatrixDouble(mat);
5166 : }
5167 : }
5168 0 : nsTextFrame::PaintTextParams params(aCtx);
5169 0 : params.framePt = gfx::Point(framePt.x, framePt.y);
5170 0 : params.dirtyRect = extraVisible;
5171 :
5172 0 : if (aBuilder->IsForGenerateGlyphMask()) {
5173 0 : MOZ_ASSERT(!aBuilder->IsForPaintingSelectionBG());
5174 0 : params.state = nsTextFrame::PaintTextParams::GenerateTextMask;
5175 0 : } else if (aBuilder->IsForPaintingSelectionBG()) {
5176 0 : params.state = nsTextFrame::PaintTextParams::PaintTextBGColor;
5177 : } else {
5178 0 : params.state = nsTextFrame::PaintTextParams::PaintText;
5179 : }
5180 :
5181 0 : f->PaintText(params, *this, mOpacity);
5182 :
5183 0 : if (willClip) {
5184 0 : aCtx->PopClip();
5185 : }
5186 : }
5187 :
5188 : void
5189 0 : nsTextFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
5190 : const nsDisplayListSet& aLists)
5191 : {
5192 0 : if (!IsVisibleForPainting(aBuilder))
5193 0 : return;
5194 :
5195 0 : DO_GLOBAL_REFLOW_COUNT_DSP("nsTextFrame");
5196 :
5197 0 : const nsStyleText* st = StyleText();
5198 : bool isTextTransparent =
5199 0 : NS_GET_A(st->mWebkitTextFillColor.CalcColor(this)) == 0 &&
5200 0 : NS_GET_A(st->mWebkitTextStrokeColor.CalcColor(this)) == 0;
5201 0 : Maybe<bool> isSelected;
5202 0 : if (((GetStateBits() & TEXT_NO_RENDERED_GLYPHS) ||
5203 0 : (isTextTransparent && !StyleText()->HasTextShadow())) &&
5204 0 : aBuilder->IsForPainting() && !nsSVGUtils::IsInSVGTextSubtree(this)) {
5205 0 : isSelected.emplace(IsSelected());
5206 0 : if (!isSelected.value()) {
5207 0 : TextDecorations textDecs;
5208 0 : GetTextDecorations(PresContext(), eResolvedColors, textDecs);
5209 0 : if (!textDecs.HasDecorationLines()) {
5210 0 : return;
5211 : }
5212 : }
5213 : }
5214 :
5215 0 : aLists.Content()->AppendToTop(
5216 0 : MakeDisplayItem<nsDisplayText>(aBuilder, this, isSelected));
5217 : }
5218 :
5219 : static nsIFrame*
5220 0 : GetGeneratedContentOwner(nsIFrame* aFrame, bool* aIsBefore)
5221 : {
5222 0 : *aIsBefore = false;
5223 0 : while (aFrame && (aFrame->GetStateBits() & NS_FRAME_GENERATED_CONTENT)) {
5224 0 : if (aFrame->Style()->GetPseudo() == nsCSSPseudoElements::before) {
5225 0 : *aIsBefore = true;
5226 : }
5227 0 : aFrame = aFrame->GetParent();
5228 : }
5229 0 : return aFrame;
5230 : }
5231 :
5232 : UniquePtr<SelectionDetails>
5233 0 : nsTextFrame::GetSelectionDetails()
5234 : {
5235 0 : const nsFrameSelection* frameSelection = GetConstFrameSelection();
5236 0 : if (frameSelection->GetTableCellSelection()) {
5237 : return nullptr;
5238 : }
5239 0 : if (!(GetStateBits() & NS_FRAME_GENERATED_CONTENT)) {
5240 : UniquePtr<SelectionDetails> details =
5241 : frameSelection->LookUpSelection(mContent, GetContentOffset(),
5242 0 : GetContentLength(), false);
5243 0 : for (SelectionDetails* sd = details.get(); sd; sd = sd->mNext.get()) {
5244 0 : sd->mStart += mContentOffset;
5245 0 : sd->mEnd += mContentOffset;
5246 : }
5247 0 : return details;
5248 : }
5249 :
5250 : // Check if the beginning or end of the element is selected, depending on
5251 : // whether we're :before content or :after content.
5252 : bool isBefore;
5253 0 : nsIFrame* owner = GetGeneratedContentOwner(this, &isBefore);
5254 0 : if (!owner || !owner->GetContent())
5255 : return nullptr;
5256 :
5257 : UniquePtr<SelectionDetails> details =
5258 : frameSelection->LookUpSelection(owner->GetContent(),
5259 0 : isBefore ? 0 : owner->GetContent()->GetChildCount(), 0, false);
5260 0 : for (SelectionDetails* sd = details.get(); sd; sd = sd->mNext.get()) {
5261 : // The entire text is selected!
5262 0 : sd->mStart = GetContentOffset();
5263 0 : sd->mEnd = GetContentEnd();
5264 : }
5265 0 : return details;
5266 : }
5267 :
5268 : static void
5269 0 : PaintSelectionBackground(DrawTarget& aDrawTarget,
5270 : nscolor aColor,
5271 : const LayoutDeviceRect& aDirtyRect,
5272 : const LayoutDeviceRect& aRect,
5273 : nsTextFrame::DrawPathCallbacks* aCallbacks)
5274 : {
5275 0 : Rect rect = aRect.Intersect(aDirtyRect).ToUnknownRect();
5276 0 : MaybeSnapToDevicePixels(rect, aDrawTarget);
5277 :
5278 0 : if (aCallbacks) {
5279 0 : aCallbacks->NotifySelectionBackgroundNeedsFill(rect, aColor, aDrawTarget);
5280 : } else {
5281 0 : ColorPattern color(ToDeviceColor(aColor));
5282 0 : aDrawTarget.FillRect(rect, color);
5283 : }
5284 0 : }
5285 :
5286 : // Attempt to get the LineBaselineOffset property of aChildFrame
5287 : // If not set, calculate this value for all child frames of aBlockFrame
5288 : static nscoord
5289 0 : LazyGetLineBaselineOffset(nsIFrame* aChildFrame, nsBlockFrame* aBlockFrame)
5290 : {
5291 : bool offsetFound;
5292 0 : nscoord offset = aChildFrame->GetProperty(
5293 0 : nsIFrame::LineBaselineOffset(), &offsetFound);
5294 :
5295 0 : if (!offsetFound) {
5296 0 : for (nsBlockFrame::LineIterator line = aBlockFrame->LinesBegin(),
5297 0 : line_end = aBlockFrame->LinesEnd();
5298 : line != line_end; line++) {
5299 0 : if (line->IsInline()) {
5300 0 : int32_t n = line->GetChildCount();
5301 0 : nscoord lineBaseline = line->BStart() + line->GetLogicalAscent();
5302 0 : for (nsIFrame* lineFrame = line->mFirstChild;
5303 0 : n > 0; lineFrame = lineFrame->GetNextSibling(), --n) {
5304 0 : offset = lineBaseline - lineFrame->GetNormalPosition().y;
5305 0 : lineFrame->SetProperty(nsIFrame::LineBaselineOffset(), offset);
5306 : }
5307 : }
5308 : }
5309 0 : return aChildFrame->GetProperty(
5310 0 : nsIFrame::LineBaselineOffset(), &offsetFound);
5311 : } else {
5312 : return offset;
5313 : }
5314 : }
5315 :
5316 0 : static bool IsUnderlineRight(nsIFrame* aFrame)
5317 : {
5318 0 : nsAtom* langAtom = aFrame->StyleFont()->mLanguage;
5319 0 : if (!langAtom) {
5320 : return false;
5321 : }
5322 0 : nsAtomString langStr(langAtom);
5323 0 : return (StringBeginsWith(langStr, NS_LITERAL_STRING("ja")) ||
5324 0 : StringBeginsWith(langStr, NS_LITERAL_STRING("ko"))) &&
5325 0 : (langStr.Length() == 2 || langStr[2] == '-');
5326 : }
5327 :
5328 : void
5329 0 : nsTextFrame::GetTextDecorations(
5330 : nsPresContext* aPresContext,
5331 : nsTextFrame::TextDecorationColorResolution aColorResolution,
5332 : nsTextFrame::TextDecorations& aDecorations)
5333 : {
5334 0 : const nsCompatibility compatMode = aPresContext->CompatibilityMode();
5335 :
5336 0 : bool useOverride = false;
5337 0 : nscolor overrideColor = NS_RGBA(0, 0, 0, 0);
5338 :
5339 0 : bool nearestBlockFound = false;
5340 : // Use writing mode of parent frame for orthogonal text frame to work.
5341 : // See comment in nsTextFrame::DrawTextRunAndDecorations.
5342 0 : WritingMode wm = GetParent()->GetWritingMode();
5343 0 : bool vertical = wm.IsVertical();
5344 :
5345 0 : nscoord ascent = GetLogicalBaseline(wm);
5346 : // physicalBlockStartOffset represents the offset from our baseline
5347 : // to f's physical block start, which is top in horizontal writing
5348 : // mode, and left in vertical writing modes, in our coordinate space.
5349 : // This physical block start is logical block start in most cases,
5350 : // but for vertical-rl, it is logical block end, and consequently in
5351 : // that case, it starts from the descent instead of ascent.
5352 : nscoord physicalBlockStartOffset =
5353 0 : wm.IsVerticalRL() ? GetSize().width - ascent : ascent;
5354 : // baselineOffset represents the offset from our baseline to f's baseline or
5355 : // the nearest block's baseline, in our coordinate space, whichever is closest
5356 : // during the particular iteration
5357 0 : nscoord baselineOffset = 0;
5358 :
5359 0 : for (nsIFrame* f = this, *fChild = nullptr;
5360 0 : f;
5361 0 : fChild = f,
5362 : f = nsLayoutUtils::GetParentOrPlaceholderFor(f))
5363 : {
5364 0 : ComputedStyle *const context = f->Style();
5365 0 : if (!context->HasTextDecorationLines()) {
5366 : break;
5367 : }
5368 :
5369 0 : const nsStyleTextReset *const styleText = context->StyleTextReset();
5370 0 : const uint8_t textDecorations = styleText->mTextDecorationLine;
5371 :
5372 0 : if (!useOverride &&
5373 : (NS_STYLE_TEXT_DECORATION_LINE_OVERRIDE_ALL & textDecorations)) {
5374 : // This handles the <a href="blah.html"><font color="green">La
5375 : // la la</font></a> case. The link underline should be green.
5376 0 : useOverride = true;
5377 : overrideColor =
5378 0 : nsLayoutUtils::GetColor(f, &nsStyleTextReset::mTextDecorationColor);
5379 : }
5380 :
5381 0 : nsBlockFrame* fBlock = nsLayoutUtils::GetAsBlock(f);
5382 0 : const bool firstBlock = !nearestBlockFound && fBlock;
5383 :
5384 : // Not updating positions once we hit a parent block is equivalent to
5385 : // the CSS 2.1 spec that blocks should propagate decorations down to their
5386 : // children (albeit the style should be preserved)
5387 : // However, if we're vertically aligned within a block, then we need to
5388 : // recover the correct baseline from the line by querying the FrameProperty
5389 : // that should be set (see nsLineLayout::VerticalAlignLine).
5390 0 : if (firstBlock) {
5391 : // At this point, fChild can't be null since TextFrames can't be blocks
5392 0 : if (fChild->VerticalAlignEnum() != NS_STYLE_VERTICAL_ALIGN_BASELINE) {
5393 :
5394 : // Since offset is the offset in the child's coordinate space, we have
5395 : // to undo the accumulation to bring the transform out of the block's
5396 : // coordinate space
5397 : const nscoord lineBaselineOffset = LazyGetLineBaselineOffset(fChild,
5398 0 : fBlock);
5399 :
5400 0 : baselineOffset = physicalBlockStartOffset - lineBaselineOffset -
5401 0 : (vertical ? fChild->GetNormalPosition().x
5402 0 : : fChild->GetNormalPosition().y);
5403 : }
5404 : }
5405 0 : else if (!nearestBlockFound) {
5406 : // offset here is the offset from f's baseline to f's top/left
5407 : // boundary. It's descent for vertical-rl, and ascent otherwise.
5408 0 : nscoord offset = wm.IsVerticalRL() ?
5409 0 : f->GetSize().width - f->GetLogicalBaseline(wm) :
5410 0 : f->GetLogicalBaseline(wm);
5411 0 : baselineOffset = physicalBlockStartOffset - offset;
5412 : }
5413 :
5414 0 : nearestBlockFound = nearestBlockFound || firstBlock;
5415 0 : physicalBlockStartOffset +=
5416 0 : vertical ? f->GetNormalPosition().x : f->GetNormalPosition().y;
5417 :
5418 0 : const uint8_t style = styleText->mTextDecorationStyle;
5419 0 : if (textDecorations) {
5420 : nscolor color;
5421 0 : if (useOverride) {
5422 : color = overrideColor;
5423 0 : } else if (nsSVGUtils::IsInSVGTextSubtree(this)) {
5424 : // XXX We might want to do something with text-decoration-color when
5425 : // painting SVG text, but it's not clear what we should do. We
5426 : // at least need SVG text decorations to paint with 'fill' if
5427 : // text-decoration-color has its initial value currentColor.
5428 : // We could choose to interpret currentColor as "currentFill"
5429 : // for SVG text, and have e.g. text-decoration-color:red to
5430 : // override the fill paint of the decoration.
5431 0 : color = aColorResolution == eResolvedColors ?
5432 : nsLayoutUtils::GetColor(f, &nsStyleSVG::mFill) :
5433 : NS_SAME_AS_FOREGROUND_COLOR;
5434 : } else {
5435 : color = nsLayoutUtils::
5436 0 : GetColor(f, &nsStyleTextReset::mTextDecorationColor);
5437 : }
5438 :
5439 0 : bool swapUnderlineAndOverline = vertical && IsUnderlineRight(f);
5440 : const uint8_t kUnderline =
5441 : swapUnderlineAndOverline ? NS_STYLE_TEXT_DECORATION_LINE_OVERLINE :
5442 0 : NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE;
5443 : const uint8_t kOverline =
5444 : swapUnderlineAndOverline ? NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE :
5445 0 : NS_STYLE_TEXT_DECORATION_LINE_OVERLINE;
5446 :
5447 0 : if (textDecorations & kUnderline) {
5448 0 : aDecorations.mUnderlines.AppendElement(
5449 0 : nsTextFrame::LineDecoration(f, baselineOffset, color, style));
5450 : }
5451 0 : if (textDecorations & kOverline) {
5452 0 : aDecorations.mOverlines.AppendElement(
5453 0 : nsTextFrame::LineDecoration(f, baselineOffset, color, style));
5454 : }
5455 0 : if (textDecorations & NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH) {
5456 0 : aDecorations.mStrikes.AppendElement(
5457 0 : nsTextFrame::LineDecoration(f, baselineOffset, color, style));
5458 : }
5459 : }
5460 :
5461 : // In all modes, if we're on an inline-block or inline-table (or
5462 : // inline-stack, inline-box, inline-grid), we're done.
5463 : // If we're on a ruby frame other than ruby text container, we
5464 : // should continue.
5465 0 : mozilla::StyleDisplay display = f->GetDisplay();
5466 0 : if (display != mozilla::StyleDisplay::Inline &&
5467 0 : (!nsStyleDisplay::IsRubyDisplayType(display) ||
5468 0 : display == mozilla::StyleDisplay::RubyTextContainer) &&
5469 0 : nsStyleDisplay::IsDisplayTypeInlineOutside(display)) {
5470 : break;
5471 : }
5472 :
5473 : // In quirks mode, if we're on an HTML table element, we're done.
5474 0 : if (compatMode == eCompatibility_NavQuirks &&
5475 0 : f->GetContent()->IsHTMLElement(nsGkAtoms::table)) {
5476 : break;
5477 : }
5478 :
5479 : // If we're on an absolutely-positioned element or a floating
5480 : // element, we're done.
5481 0 : if (f->IsFloating() || f->IsAbsolutelyPositioned()) {
5482 : break;
5483 : }
5484 :
5485 : // If we're an outer <svg> element, which is classified as an atomic
5486 : // inline-level element, we're done.
5487 0 : if (f->IsSVGOuterSVGFrame()) {
5488 : break;
5489 : }
5490 : }
5491 0 : }
5492 :
5493 : static float
5494 0 : GetInflationForTextDecorations(nsIFrame* aFrame, nscoord aInflationMinFontSize)
5495 : {
5496 0 : if (nsSVGUtils::IsInSVGTextSubtree(aFrame)) {
5497 : const nsIFrame* container = aFrame;
5498 0 : while (!container->IsSVGTextFrame()) {
5499 0 : container = container->GetParent();
5500 : }
5501 0 : NS_ASSERTION(container, "expected to find an ancestor SVGTextFrame");
5502 : return
5503 0 : static_cast<const SVGTextFrame*>(container)->GetFontSizeScaleFactor();
5504 : }
5505 0 : return nsLayoutUtils::FontSizeInflationInner(aFrame, aInflationMinFontSize);
5506 : }
5507 :
5508 0 : struct EmphasisMarkInfo
5509 : {
5510 : RefPtr<gfxTextRun> textRun;
5511 : gfxFloat advance;
5512 : gfxFloat baselineOffset;
5513 : };
5514 :
5515 : NS_DECLARE_FRAME_PROPERTY_DELETABLE(EmphasisMarkProperty, EmphasisMarkInfo)
5516 :
5517 : static already_AddRefed<gfxTextRun>
5518 0 : GenerateTextRunForEmphasisMarks(nsTextFrame* aFrame,
5519 : nsFontMetrics* aFontMetrics,
5520 : ComputedStyle* aComputedStyle,
5521 : const nsStyleText* aStyleText)
5522 : {
5523 0 : const nsString& emphasisString = aStyleText->mTextEmphasisStyleString;
5524 0 : RefPtr<DrawTarget> dt = CreateReferenceDrawTarget(aFrame);
5525 0 : auto appUnitsPerDevUnit = aFrame->PresContext()->AppUnitsPerDevPixel();
5526 0 : gfx::ShapedTextFlags flags = nsLayoutUtils::GetTextRunOrientFlagsForStyle(aComputedStyle);
5527 0 : if (flags == gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_MIXED) {
5528 : // The emphasis marks should always be rendered upright per spec.
5529 0 : flags = gfx::ShapedTextFlags::TEXT_ORIENT_VERTICAL_UPRIGHT;
5530 : }
5531 : return aFontMetrics->GetThebesFontGroup()->
5532 : MakeTextRun<char16_t>(emphasisString.get(), emphasisString.Length(),
5533 : dt, appUnitsPerDevUnit, flags,
5534 0 : nsTextFrameUtils::Flags(), nullptr);
5535 : }
5536 :
5537 : static nsRubyFrame*
5538 0 : FindFurthestInlineRubyAncestor(nsTextFrame* aFrame)
5539 : {
5540 0 : nsRubyFrame* rubyFrame = nullptr;
5541 0 : for (nsIFrame* frame = aFrame->GetParent();
5542 0 : frame && frame->IsFrameOfType(nsIFrame::eLineParticipant);
5543 : frame = frame->GetParent()) {
5544 0 : if (frame->IsRubyFrame()) {
5545 0 : rubyFrame = static_cast<nsRubyFrame*>(frame);
5546 : }
5547 : }
5548 0 : return rubyFrame;
5549 : }
5550 :
5551 : nsRect
5552 0 : nsTextFrame::UpdateTextEmphasis(WritingMode aWM, PropertyProvider& aProvider)
5553 : {
5554 0 : const nsStyleText* styleText = StyleText();
5555 0 : if (!styleText->HasTextEmphasis()) {
5556 0 : DeleteProperty(EmphasisMarkProperty());
5557 0 : return nsRect();
5558 : }
5559 :
5560 0 : ComputedStyle* computedStyle = Style();
5561 0 : bool isTextCombined = computedStyle->IsTextCombined();
5562 0 : if (isTextCombined) {
5563 0 : computedStyle = GetParent()->Style();
5564 : }
5565 : RefPtr<nsFontMetrics> fm =
5566 0 : nsLayoutUtils::GetFontMetricsOfEmphasisMarks(computedStyle,
5567 : PresContext(),
5568 0 : GetFontSizeInflation());
5569 0 : EmphasisMarkInfo* info = new EmphasisMarkInfo;
5570 : info->textRun =
5571 0 : GenerateTextRunForEmphasisMarks(this, fm, computedStyle, styleText);
5572 0 : info->advance = info->textRun->GetAdvanceWidth();
5573 :
5574 : // Calculate the baseline offset
5575 0 : LogicalSide side = styleText->TextEmphasisSide(aWM);
5576 0 : LogicalSize frameSize = GetLogicalSize(aWM);
5577 : // The overflow rect is inflated in the inline direction by half
5578 : // advance of the emphasis mark on each side, so that even if a mark
5579 : // is drawn for a zero-width character, it won't be clipped.
5580 0 : LogicalRect overflowRect(aWM, -info->advance / 2,
5581 : /* BStart to be computed below */ 0,
5582 0 : frameSize.ISize(aWM) + info->advance,
5583 0 : fm->MaxAscent() + fm->MaxDescent());
5584 : RefPtr<nsFontMetrics> baseFontMetrics = isTextCombined
5585 0 : ? nsLayoutUtils::GetInflatedFontMetricsForFrame(GetParent())
5586 0 : : do_AddRef(aProvider.GetFontMetrics());
5587 : // When the writing mode is vertical-lr the line is inverted, and thus
5588 : // the ascent and descent are swapped.
5589 0 : nscoord absOffset = (side == eLogicalSideBStart) != aWM.IsLineInverted() ?
5590 0 : baseFontMetrics->MaxAscent() + fm->MaxDescent() :
5591 0 : baseFontMetrics->MaxDescent() + fm->MaxAscent();
5592 0 : RubyBlockLeadings leadings;
5593 0 : if (nsRubyFrame* ruby = FindFurthestInlineRubyAncestor(this)) {
5594 0 : leadings = ruby->GetBlockLeadings();
5595 : }
5596 0 : if (side == eLogicalSideBStart) {
5597 0 : info->baselineOffset = -absOffset - leadings.mStart;
5598 0 : overflowRect.BStart(aWM) = -overflowRect.BSize(aWM) - leadings.mStart;
5599 : } else {
5600 0 : MOZ_ASSERT(side == eLogicalSideBEnd);
5601 0 : info->baselineOffset = absOffset + leadings.mEnd;
5602 0 : overflowRect.BStart(aWM) = frameSize.BSize(aWM) + leadings.mEnd;
5603 : }
5604 : // If text combined, fix the gap between the text frame and its parent.
5605 0 : if (isTextCombined) {
5606 0 : nscoord gap = (baseFontMetrics->MaxHeight() - frameSize.BSize(aWM)) / 2;
5607 0 : overflowRect.BStart(aWM) += gap * (side == eLogicalSideBStart ? -1 : 1);
5608 : }
5609 :
5610 0 : SetProperty(EmphasisMarkProperty(), info);
5611 0 : return overflowRect.GetPhysicalRect(aWM, frameSize.GetPhysicalSize(aWM));
5612 : }
5613 :
5614 : void
5615 0 : nsTextFrame::UnionAdditionalOverflow(nsPresContext* aPresContext,
5616 : nsIFrame* aBlock,
5617 : PropertyProvider& aProvider,
5618 : nsRect* aVisualOverflowRect,
5619 : bool aIncludeTextDecorations)
5620 : {
5621 0 : const WritingMode wm = GetWritingMode();
5622 0 : bool verticalRun = mTextRun->IsVertical();
5623 0 : const gfxFloat appUnitsPerDevUnit = aPresContext->AppUnitsPerDevPixel();
5624 :
5625 0 : if (IsFloatingFirstLetterChild()) {
5626 0 : bool inverted = wm.IsLineInverted();
5627 : // The underline/overline drawable area must be contained in the overflow
5628 : // rect when this is in floating first letter frame at *both* modes.
5629 : // In this case, aBlock is the ::first-letter frame.
5630 : uint8_t decorationStyle = aBlock->Style()->
5631 0 : StyleTextReset()->mTextDecorationStyle;
5632 : // If the style is none, let's include decoration line rect as solid style
5633 : // since changing the style from none to solid/dotted/dashed doesn't cause
5634 : // reflow.
5635 0 : if (decorationStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
5636 0 : decorationStyle = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
5637 : }
5638 0 : nsFontMetrics* fontMetrics = aProvider.GetFontMetrics();
5639 : nscoord underlineOffset, underlineSize;
5640 0 : fontMetrics->GetUnderline(underlineOffset, underlineSize);
5641 0 : nscoord maxAscent = inverted ? fontMetrics->MaxDescent()
5642 0 : : fontMetrics->MaxAscent();
5643 :
5644 0 : nsCSSRendering::DecorationRectParams params;
5645 : Float gfxWidth =
5646 0 : (verticalRun ? aVisualOverflowRect->height
5647 0 : : aVisualOverflowRect->width) /
5648 0 : appUnitsPerDevUnit;
5649 0 : params.lineSize = Size(gfxWidth, underlineSize / appUnitsPerDevUnit);
5650 0 : params.ascent = gfxFloat(mAscent) / appUnitsPerDevUnit;
5651 0 : params.style = decorationStyle;
5652 0 : params.vertical = verticalRun;
5653 0 : params.sidewaysLeft = mTextRun->IsSidewaysLeft();
5654 :
5655 0 : params.offset = underlineOffset / appUnitsPerDevUnit;
5656 0 : params.decoration = NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE;
5657 : nsRect underlineRect =
5658 0 : nsCSSRendering::GetTextDecorationRect(aPresContext, params);
5659 0 : params.offset = maxAscent / appUnitsPerDevUnit;
5660 0 : params.decoration = NS_STYLE_TEXT_DECORATION_LINE_OVERLINE;
5661 : nsRect overlineRect =
5662 0 : nsCSSRendering::GetTextDecorationRect(aPresContext, params);
5663 :
5664 0 : aVisualOverflowRect->UnionRect(*aVisualOverflowRect, underlineRect);
5665 0 : aVisualOverflowRect->UnionRect(*aVisualOverflowRect, overlineRect);
5666 :
5667 : // XXX If strikeoutSize is much thicker than the underlineSize, it may
5668 : // cause overflowing from the overflow rect. However, such case
5669 : // isn't realistic, we don't need to compute it now.
5670 : }
5671 0 : if (aIncludeTextDecorations) {
5672 : // Use writing mode of parent frame for orthogonal text frame to
5673 : // work. See comment in nsTextFrame::DrawTextRunAndDecorations.
5674 0 : WritingMode parentWM = GetParent()->GetWritingMode();
5675 0 : bool verticalDec = parentWM.IsVertical();
5676 : bool useVerticalMetrics = verticalDec != verticalRun
5677 0 : ? verticalDec : verticalRun && mTextRun->UseCenterBaseline();
5678 :
5679 : // Since CSS 2.1 requires that text-decoration defined on ancestors maintain
5680 : // style and position, they can be drawn at virtually any y-offset, so
5681 : // maxima and minima are required to reliably generate the rectangle for
5682 : // them
5683 0 : TextDecorations textDecs;
5684 0 : GetTextDecorations(aPresContext, eResolvedColors, textDecs);
5685 0 : if (textDecs.HasDecorationLines()) {
5686 : nscoord inflationMinFontSize =
5687 0 : nsLayoutUtils::InflationMinFontSizeFor(aBlock);
5688 :
5689 0 : const nscoord measure = verticalDec ? GetSize().height : GetSize().width;
5690 0 : gfxFloat gfxWidth = measure / appUnitsPerDevUnit;
5691 0 : gfxFloat ascent = gfxFloat(GetLogicalBaseline(parentWM))
5692 0 : / appUnitsPerDevUnit;
5693 0 : nscoord frameBStart = 0;
5694 0 : if (parentWM.IsVerticalRL()) {
5695 0 : frameBStart = GetSize().width;
5696 0 : ascent = -ascent;
5697 : }
5698 :
5699 0 : nsCSSRendering::DecorationRectParams params;
5700 0 : params.lineSize = Size(gfxWidth, 0);
5701 0 : params.ascent = ascent;
5702 0 : params.vertical = verticalDec;
5703 0 : params.sidewaysLeft = mTextRun->IsSidewaysLeft();
5704 :
5705 0 : nscoord topOrLeft(nscoord_MAX), bottomOrRight(nscoord_MIN);
5706 : typedef gfxFont::Metrics Metrics;
5707 : auto accumulateDecorationRect = [&](const LineDecoration& dec,
5708 : gfxFloat Metrics::* lineSize,
5709 0 : gfxFloat Metrics::* lineOffset) {
5710 0 : params.style = dec.mStyle;
5711 : // If the style is solid, let's include decoration line rect of solid
5712 : // style since changing the style from none to solid/dotted/dashed
5713 : // doesn't cause reflow.
5714 0 : if (params.style == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
5715 0 : params.style = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
5716 : }
5717 :
5718 : float inflation =
5719 0 : GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
5720 : const Metrics metrics =
5721 0 : GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation),
5722 0 : useVerticalMetrics);
5723 :
5724 0 : params.lineSize.height = metrics.*lineSize;
5725 0 : params.offset = metrics.*lineOffset;
5726 : const nsRect decorationRect =
5727 0 : nsCSSRendering::GetTextDecorationRect(aPresContext, params) +
5728 0 : (verticalDec ? nsPoint(frameBStart - dec.mBaselineOffset, 0)
5729 0 : : nsPoint(0, -dec.mBaselineOffset));
5730 :
5731 0 : if (verticalDec) {
5732 0 : topOrLeft = std::min(decorationRect.x, topOrLeft);
5733 0 : bottomOrRight = std::max(decorationRect.XMost(), bottomOrRight);
5734 : } else {
5735 0 : topOrLeft = std::min(decorationRect.y, topOrLeft);
5736 0 : bottomOrRight = std::max(decorationRect.YMost(), bottomOrRight);
5737 : }
5738 0 : };
5739 :
5740 : // Below we loop through all text decorations and compute the rectangle
5741 : // containing all of them, in this frame's coordinate space
5742 0 : params.decoration = NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE;
5743 0 : for (const LineDecoration& dec : textDecs.mUnderlines) {
5744 : accumulateDecorationRect(dec, &Metrics::underlineSize,
5745 0 : &Metrics::underlineOffset);
5746 : }
5747 0 : params.decoration = NS_STYLE_TEXT_DECORATION_LINE_OVERLINE;
5748 0 : for (const LineDecoration& dec : textDecs.mOverlines) {
5749 : accumulateDecorationRect(dec, &Metrics::underlineSize,
5750 0 : &Metrics::maxAscent);
5751 : }
5752 0 : params.decoration = NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH;
5753 0 : for (const LineDecoration& dec : textDecs.mStrikes) {
5754 : accumulateDecorationRect(dec, &Metrics::strikeoutSize,
5755 0 : &Metrics::strikeoutOffset);
5756 : }
5757 :
5758 : aVisualOverflowRect->UnionRect(
5759 : *aVisualOverflowRect,
5760 0 : verticalDec ? nsRect(topOrLeft, 0, bottomOrRight - topOrLeft, measure)
5761 0 : : nsRect(0, topOrLeft, measure, bottomOrRight - topOrLeft));
5762 : }
5763 :
5764 : aVisualOverflowRect->UnionRect(*aVisualOverflowRect,
5765 0 : UpdateTextEmphasis(parentWM, aProvider));
5766 : }
5767 :
5768 : // text-stroke overflows: add half of text-stroke-width on all sides
5769 0 : nscoord textStrokeWidth = StyleText()->mWebkitTextStrokeWidth;
5770 0 : if (textStrokeWidth > 0) {
5771 : // Inflate rect by stroke-width/2; we add an extra pixel to allow for
5772 : // antialiasing, rounding errors, etc.
5773 0 : nsRect strokeRect = *aVisualOverflowRect;
5774 0 : strokeRect.Inflate(textStrokeWidth / 2 + appUnitsPerDevUnit);
5775 0 : aVisualOverflowRect->UnionRect(*aVisualOverflowRect, strokeRect);
5776 : }
5777 :
5778 : // Text-shadow overflows
5779 : nsRect shadowRect =
5780 0 : nsLayoutUtils::GetTextShadowRectsUnion(*aVisualOverflowRect, this);
5781 0 : aVisualOverflowRect->UnionRect(*aVisualOverflowRect, shadowRect);
5782 :
5783 : // When this frame is not selected, the text-decoration area must be in
5784 : // frame bounds.
5785 0 : if (!IsSelected() ||
5786 0 : !CombineSelectionUnderlineRect(aPresContext, *aVisualOverflowRect))
5787 0 : return;
5788 0 : AddStateBits(TEXT_SELECTION_UNDERLINE_OVERFLOWED);
5789 : }
5790 :
5791 : gfxFloat
5792 0 : nsTextFrame::ComputeDescentLimitForSelectionUnderline(
5793 : nsPresContext* aPresContext,
5794 : const gfxFont::Metrics& aFontMetrics)
5795 : {
5796 0 : gfxFloat app = aPresContext->AppUnitsPerDevPixel();
5797 : nscoord lineHeightApp =
5798 0 : ReflowInput::CalcLineHeight(GetContent(),
5799 : Style(),
5800 : PresContext(),
5801 : NS_AUTOHEIGHT,
5802 0 : GetFontSizeInflation());
5803 0 : gfxFloat lineHeight = gfxFloat(lineHeightApp) / app;
5804 0 : if (lineHeight <= aFontMetrics.maxHeight) {
5805 0 : return aFontMetrics.maxDescent;
5806 : }
5807 0 : return aFontMetrics.maxDescent + (lineHeight - aFontMetrics.maxHeight) / 2;
5808 : }
5809 :
5810 : // Make sure this stays in sync with DrawSelectionDecorations below
5811 0 : static const SelectionTypeMask kSelectionTypesWithDecorations =
5812 0 : ToSelectionTypeMask(SelectionType::eSpellCheck) |
5813 0 : ToSelectionTypeMask(SelectionType::eURLStrikeout) |
5814 0 : ToSelectionTypeMask(SelectionType::eIMERawClause) |
5815 0 : ToSelectionTypeMask(SelectionType::eIMESelectedRawClause) |
5816 0 : ToSelectionTypeMask(SelectionType::eIMEConvertedClause) |
5817 0 : ToSelectionTypeMask(SelectionType::eIMESelectedClause);
5818 :
5819 : /* static */
5820 : gfxFloat
5821 0 : nsTextFrame::ComputeSelectionUnderlineHeight(
5822 : nsPresContext* aPresContext,
5823 : const gfxFont::Metrics& aFontMetrics,
5824 : SelectionType aSelectionType)
5825 : {
5826 0 : switch (aSelectionType) {
5827 : case SelectionType::eIMERawClause:
5828 : case SelectionType::eIMESelectedRawClause:
5829 : case SelectionType::eIMEConvertedClause:
5830 : case SelectionType::eIMESelectedClause:
5831 0 : return aFontMetrics.underlineSize;
5832 : case SelectionType::eSpellCheck: {
5833 : // The thickness of the spellchecker underline shouldn't honor the font
5834 : // metrics. It should be constant pixels value which is decided from the
5835 : // default font size. Note that if the actual font size is smaller than
5836 : // the default font size, we should use the actual font size because the
5837 : // computed value from the default font size can be too thick for the
5838 : // current font size.
5839 0 : nscoord defaultFontSize = aPresContext->GetDefaultFont(
5840 0 : kPresContext_DefaultVariableFont_ID, nullptr)->size;
5841 0 : int32_t zoomedFontSize = aPresContext->AppUnitsToDevPixels(
5842 0 : nsStyleFont::ZoomText(aPresContext, defaultFontSize));
5843 0 : gfxFloat fontSize = std::min(gfxFloat(zoomedFontSize),
5844 0 : aFontMetrics.emHeight);
5845 0 : fontSize = std::max(fontSize, 1.0);
5846 0 : return ceil(fontSize / 20);
5847 : }
5848 : default:
5849 0 : NS_WARNING("Requested underline style is not valid");
5850 0 : return aFontMetrics.underlineSize;
5851 : }
5852 : }
5853 :
5854 : enum class DecorationType
5855 : {
5856 : Normal, Selection
5857 : };
5858 0 : struct nsTextFrame::PaintDecorationLineParams
5859 : : nsCSSRendering::DecorationRectParams
5860 : {
5861 : gfxContext* context = nullptr;
5862 : LayoutDeviceRect dirtyRect;
5863 : Point pt;
5864 : const nscolor* overrideColor = nullptr;
5865 : nscolor color = NS_RGBA(0, 0, 0, 0);
5866 : gfxFloat icoordInFrame = 0.0f;
5867 : DecorationType decorationType = DecorationType::Normal;
5868 : DrawPathCallbacks* callbacks = nullptr;
5869 : };
5870 :
5871 : void
5872 0 : nsTextFrame::PaintDecorationLine(const PaintDecorationLineParams& aParams)
5873 : {
5874 0 : nsCSSRendering::PaintDecorationLineParams params;
5875 0 : static_cast<nsCSSRendering::DecorationRectParams&>(params) = aParams;
5876 0 : params.dirtyRect = aParams.dirtyRect.ToUnknownRect();
5877 0 : params.pt = aParams.pt;
5878 0 : params.color = aParams.overrideColor ? *aParams.overrideColor : aParams.color;
5879 0 : params.icoordInFrame = Float(aParams.icoordInFrame);
5880 0 : if (aParams.callbacks) {
5881 0 : Rect path = nsCSSRendering::DecorationLineToPath(params);
5882 0 : if (aParams.decorationType == DecorationType::Normal) {
5883 0 : aParams.callbacks->PaintDecorationLine(path, params.color);
5884 : } else {
5885 0 : aParams.callbacks->PaintSelectionDecorationLine(path, params.color);
5886 : }
5887 : } else {
5888 0 : nsCSSRendering::PaintDecorationLine(
5889 0 : this, *aParams.context->GetDrawTarget(), params);
5890 : }
5891 0 : }
5892 :
5893 : /**
5894 : * This, plus kSelectionTypesWithDecorations, encapsulates all knowledge
5895 : * about drawing text decoration for selections.
5896 : */
5897 : void
5898 0 : nsTextFrame::DrawSelectionDecorations(gfxContext* aContext,
5899 : const LayoutDeviceRect& aDirtyRect,
5900 : SelectionType aSelectionType,
5901 : nsTextPaintStyle& aTextPaintStyle,
5902 : const TextRangeStyle &aRangeStyle,
5903 : const Point& aPt,
5904 : gfxFloat aICoordInFrame,
5905 : gfxFloat aWidth,
5906 : gfxFloat aAscent,
5907 : const gfxFont::Metrics& aFontMetrics,
5908 : DrawPathCallbacks* aCallbacks,
5909 : bool aVertical,
5910 : uint8_t aDecoration)
5911 : {
5912 0 : PaintDecorationLineParams params;
5913 0 : params.context = aContext;
5914 0 : params.dirtyRect = aDirtyRect;
5915 0 : params.pt = aPt;
5916 0 : params.lineSize.width = aWidth;
5917 0 : params.ascent = aAscent;
5918 0 : params.offset = aDecoration == NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE ?
5919 : aFontMetrics.underlineOffset : aFontMetrics.maxAscent;
5920 0 : params.decoration = aDecoration;
5921 0 : params.decorationType = DecorationType::Selection;
5922 0 : params.callbacks = aCallbacks;
5923 0 : params.vertical = aVertical;
5924 0 : params.sidewaysLeft = mTextRun->IsSidewaysLeft();
5925 0 : params.descentLimit =
5926 0 : ComputeDescentLimitForSelectionUnderline(aTextPaintStyle.PresContext(),
5927 : aFontMetrics);
5928 :
5929 : float relativeSize;
5930 :
5931 0 : switch (aSelectionType) {
5932 : case SelectionType::eIMERawClause:
5933 : case SelectionType::eIMESelectedRawClause:
5934 : case SelectionType::eIMEConvertedClause:
5935 : case SelectionType::eIMESelectedClause:
5936 : case SelectionType::eSpellCheck: {
5937 : int32_t index = nsTextPaintStyle::
5938 0 : GetUnderlineStyleIndexForSelectionType(aSelectionType);
5939 : bool weDefineSelectionUnderline =
5940 : aTextPaintStyle.GetSelectionUnderlineForPaint(index, ¶ms.color,
5941 : &relativeSize,
5942 0 : ¶ms.style);
5943 0 : params.lineSize.height =
5944 0 : ComputeSelectionUnderlineHeight(aTextPaintStyle.PresContext(),
5945 : aFontMetrics, aSelectionType);
5946 0 : bool isIMEType = aSelectionType != SelectionType::eSpellCheck;
5947 :
5948 0 : if (isIMEType) {
5949 : // IME decoration lines should not be drawn on the both ends, i.e., we
5950 : // need to cut both edges of the decoration lines. Because same style
5951 : // IME selections can adjoin, but the users need to be able to know
5952 : // where are the boundaries of the selections.
5953 : //
5954 : // X: underline
5955 : //
5956 : // IME selection #1 IME selection #2 IME selection #3
5957 : // | | |
5958 : // | XXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXXX | XXXXXXXXXXXXXXXXXXX
5959 : // +---------------------+----------------------+--------------------
5960 : // ^ ^ ^ ^ ^
5961 : // gap gap gap
5962 0 : params.pt.x += 1.0;
5963 0 : params.lineSize.width -= 2.0;
5964 : }
5965 0 : if (isIMEType && aRangeStyle.IsDefined()) {
5966 : // If IME defines the style, that should override our definition.
5967 0 : if (aRangeStyle.IsLineStyleDefined()) {
5968 0 : if (aRangeStyle.mLineStyle == TextRangeStyle::LINESTYLE_NONE) {
5969 0 : return;
5970 : }
5971 0 : params.style = aRangeStyle.mLineStyle;
5972 0 : relativeSize = aRangeStyle.mIsBoldLine ? 2.0f : 1.0f;
5973 0 : } else if (!weDefineSelectionUnderline) {
5974 : // There is no underline style definition.
5975 : return;
5976 : }
5977 : // If underline color is defined and that doesn't depend on the
5978 : // foreground color, we should use the color directly.
5979 0 : if (aRangeStyle.IsUnderlineColorDefined() &&
5980 0 : (!aRangeStyle.IsForegroundColorDefined() ||
5981 0 : aRangeStyle.mUnderlineColor != aRangeStyle.mForegroundColor)) {
5982 0 : params.color = aRangeStyle.mUnderlineColor;
5983 : }
5984 : // If foreground color or background color is defined, the both colors
5985 : // are computed by GetSelectionTextColors(). Then, we should use its
5986 : // foreground color always. The color should have sufficient contrast
5987 : // with the background color.
5988 0 : else if (aRangeStyle.IsForegroundColorDefined() ||
5989 0 : aRangeStyle.IsBackgroundColorDefined()) {
5990 : nscolor bg;
5991 : GetSelectionTextColors(aSelectionType, aTextPaintStyle,
5992 0 : aRangeStyle, ¶ms.color, &bg);
5993 : }
5994 : // Otherwise, use the foreground color of the frame.
5995 : else {
5996 0 : params.color = aTextPaintStyle.GetTextColor();
5997 : }
5998 0 : } else if (!weDefineSelectionUnderline) {
5999 : // IME doesn't specify the selection style and we don't define selection
6000 : // underline.
6001 : return;
6002 : }
6003 : break;
6004 : }
6005 : case SelectionType::eURLStrikeout: {
6006 : nscoord inflationMinFontSize =
6007 0 : nsLayoutUtils::InflationMinFontSizeFor(this);
6008 : float inflation =
6009 0 : GetInflationForTextDecorations(this, inflationMinFontSize);
6010 : const gfxFont::Metrics metrics =
6011 0 : GetFirstFontMetrics(GetFontGroupForFrame(this, inflation), aVertical);
6012 :
6013 0 : relativeSize = 2.0f;
6014 0 : aTextPaintStyle.GetURLSecondaryColor(¶ms.color);
6015 0 : params.style = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
6016 0 : params.lineSize.height = metrics.strikeoutSize;
6017 0 : params.offset = metrics.strikeoutOffset + 0.5;
6018 0 : params.decoration = NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH;
6019 : break;
6020 : }
6021 : default:
6022 0 : NS_WARNING("Requested selection decorations when there aren't any");
6023 0 : return;
6024 : }
6025 0 : params.lineSize.height *= relativeSize;
6026 0 : params.icoordInFrame = (aVertical ? params.pt.y - aPt.y
6027 0 : : params.pt.x - aPt.x) + aICoordInFrame;
6028 0 : PaintDecorationLine(params);
6029 : }
6030 :
6031 : /* static */
6032 : bool
6033 0 : nsTextFrame::GetSelectionTextColors(SelectionType aSelectionType,
6034 : nsTextPaintStyle& aTextPaintStyle,
6035 : const TextRangeStyle &aRangeStyle,
6036 : nscolor* aForeground,
6037 : nscolor* aBackground)
6038 : {
6039 0 : switch (aSelectionType) {
6040 : case SelectionType::eNormal:
6041 0 : return aTextPaintStyle.GetSelectionColors(aForeground, aBackground);
6042 : case SelectionType::eFind:
6043 0 : aTextPaintStyle.GetHighlightColors(aForeground, aBackground);
6044 0 : return true;
6045 : case SelectionType::eURLSecondary:
6046 0 : aTextPaintStyle.GetURLSecondaryColor(aForeground);
6047 0 : *aBackground = NS_RGBA(0,0,0,0);
6048 0 : return true;
6049 : case SelectionType::eIMERawClause:
6050 : case SelectionType::eIMESelectedRawClause:
6051 : case SelectionType::eIMEConvertedClause:
6052 : case SelectionType::eIMESelectedClause:
6053 0 : if (aRangeStyle.IsDefined()) {
6054 0 : if (!aRangeStyle.IsForegroundColorDefined() &&
6055 0 : !aRangeStyle.IsBackgroundColorDefined()) {
6056 0 : *aForeground = aTextPaintStyle.GetTextColor();
6057 0 : *aBackground = NS_RGBA(0,0,0,0);
6058 0 : return false;
6059 : }
6060 0 : if (aRangeStyle.IsForegroundColorDefined()) {
6061 0 : *aForeground = aRangeStyle.mForegroundColor;
6062 0 : if (aRangeStyle.IsBackgroundColorDefined()) {
6063 0 : *aBackground = aRangeStyle.mBackgroundColor;
6064 : } else {
6065 : // If foreground color is defined but background color isn't
6066 : // defined, we can guess that IME must expect that the background
6067 : // color is system's default field background color.
6068 0 : *aBackground = aTextPaintStyle.GetSystemFieldBackgroundColor();
6069 : }
6070 : } else { // aRangeStyle.IsBackgroundColorDefined() is true
6071 0 : *aBackground = aRangeStyle.mBackgroundColor;
6072 : // If background color is defined but foreground color isn't defined,
6073 : // we can assume that IME must expect that the foreground color is
6074 : // same as system's field text color.
6075 0 : *aForeground = aTextPaintStyle.GetSystemFieldForegroundColor();
6076 : }
6077 : return true;
6078 : }
6079 0 : aTextPaintStyle.GetIMESelectionColors(
6080 : nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(
6081 : aSelectionType),
6082 0 : aForeground, aBackground);
6083 0 : return true;
6084 : default:
6085 0 : *aForeground = aTextPaintStyle.GetTextColor();
6086 0 : *aBackground = NS_RGBA(0,0,0,0);
6087 0 : return false;
6088 : }
6089 : }
6090 :
6091 : /**
6092 : * This sets *aShadow to the appropriate shadow, if any, for the given
6093 : * type of selection. Returns true if *aShadow was set.
6094 : * If text-shadow was not specified, *aShadow is left untouched
6095 : * (NOT reset to null), and the function returns false.
6096 : */
6097 0 : static bool GetSelectionTextShadow(nsIFrame* aFrame,
6098 : SelectionType aSelectionType,
6099 : nsTextPaintStyle& aTextPaintStyle,
6100 : nsCSSShadowArray** aShadow)
6101 : {
6102 0 : switch (aSelectionType) {
6103 : case SelectionType::eNormal:
6104 0 : return aTextPaintStyle.GetSelectionShadow(aShadow);
6105 : default:
6106 : return false;
6107 : }
6108 : }
6109 :
6110 : /**
6111 : * This class lets us iterate over chunks of text in a uniform selection state,
6112 : * observing cluster boundaries, in content order, maintaining the current
6113 : * x-offset as we go, and telling whether the text chunk has a hyphen after
6114 : * it or not. The caller is responsible for actually computing the advance
6115 : * width of each chunk.
6116 : */
6117 0 : class SelectionIterator {
6118 : public:
6119 : /**
6120 : * aStart and aLength are in the original string. aSelectionDetails is
6121 : * according to the original string.
6122 : * @param aXOffset the offset from the origin of the frame to the start
6123 : * of the text (the left baseline origin for LTR, the right baseline origin
6124 : * for RTL)
6125 : */
6126 : SelectionIterator(SelectionDetails** aSelectionDetails,
6127 : gfxTextRun::Range aRange, PropertyProvider& aProvider,
6128 : gfxTextRun* aTextRun, gfxFloat aXOffset);
6129 :
6130 : /**
6131 : * Returns the next segment of uniformly selected (or not) text.
6132 : * @param aXOffset the offset from the origin of the frame to the start
6133 : * of the text (the left baseline origin for LTR, the right baseline origin
6134 : * for RTL)
6135 : * @param aRange the transformed string range of the text for this segment
6136 : * @param aHyphenWidth if a hyphen is to be rendered after the text, the
6137 : * width of the hyphen, otherwise zero
6138 : * @param aSelectionType the selection type for this segment
6139 : * @param aStyle the selection style for this segment
6140 : * @return false if there are no more segments
6141 : */
6142 : bool GetNextSegment(gfxFloat* aXOffset, gfxTextRun::Range* aRange,
6143 : gfxFloat* aHyphenWidth,
6144 : SelectionType* aSelectionType,
6145 : TextRangeStyle* aStyle);
6146 0 : void UpdateWithAdvance(gfxFloat aAdvance) {
6147 0 : mXOffset += aAdvance*mTextRun->GetDirection();
6148 0 : }
6149 :
6150 : private:
6151 : SelectionDetails** mSelectionDetails;
6152 : PropertyProvider& mProvider;
6153 : RefPtr<gfxTextRun> mTextRun;
6154 : gfxSkipCharsIterator mIterator;
6155 : gfxTextRun::Range mOriginalRange;
6156 : gfxFloat mXOffset;
6157 : };
6158 :
6159 0 : SelectionIterator::SelectionIterator(SelectionDetails** aSelectionDetails,
6160 : gfxTextRun::Range aRange,
6161 : PropertyProvider& aProvider,
6162 0 : gfxTextRun* aTextRun, gfxFloat aXOffset)
6163 : : mSelectionDetails(aSelectionDetails), mProvider(aProvider),
6164 : mTextRun(aTextRun), mIterator(aProvider.GetStart()),
6165 0 : mOriginalRange(aRange), mXOffset(aXOffset)
6166 : {
6167 0 : mIterator.SetOriginalOffset(aRange.start);
6168 0 : }
6169 :
6170 0 : bool SelectionIterator::GetNextSegment(gfxFloat* aXOffset,
6171 : gfxTextRun::Range* aRange,
6172 : gfxFloat* aHyphenWidth,
6173 : SelectionType* aSelectionType,
6174 : TextRangeStyle* aStyle)
6175 : {
6176 0 : if (mIterator.GetOriginalOffset() >= int32_t(mOriginalRange.end))
6177 : return false;
6178 :
6179 : // save offset into transformed string now
6180 0 : uint32_t runOffset = mIterator.GetSkippedOffset();
6181 :
6182 0 : uint32_t index = mIterator.GetOriginalOffset() - mOriginalRange.start;
6183 0 : SelectionDetails* sdptr = mSelectionDetails[index];
6184 : SelectionType selectionType =
6185 0 : sdptr ? sdptr->mSelectionType : SelectionType::eNone;
6186 0 : TextRangeStyle style;
6187 0 : if (sdptr) {
6188 0 : style = sdptr->mTextRangeStyle;
6189 : }
6190 0 : for (++index; index < mOriginalRange.Length(); ++index) {
6191 0 : if (sdptr != mSelectionDetails[index])
6192 : break;
6193 : }
6194 0 : mIterator.SetOriginalOffset(index + mOriginalRange.start);
6195 :
6196 : // Advance to the next cluster boundary
6197 0 : while (mIterator.GetOriginalOffset() < int32_t(mOriginalRange.end) &&
6198 0 : !mIterator.IsOriginalCharSkipped() &&
6199 0 : !mTextRun->IsClusterStart(mIterator.GetSkippedOffset())) {
6200 0 : mIterator.AdvanceOriginal(1);
6201 : }
6202 :
6203 : bool haveHyphenBreak =
6204 0 : (mProvider.GetFrame()->GetStateBits() & TEXT_HYPHEN_BREAK) != 0;
6205 0 : aRange->start = runOffset;
6206 0 : aRange->end = mIterator.GetSkippedOffset();
6207 0 : *aXOffset = mXOffset;
6208 0 : *aHyphenWidth = 0;
6209 0 : if (mIterator.GetOriginalOffset() == int32_t(mOriginalRange.end) &&
6210 : haveHyphenBreak) {
6211 0 : *aHyphenWidth = mProvider.GetHyphenWidth();
6212 : }
6213 0 : *aSelectionType = selectionType;
6214 0 : *aStyle = style;
6215 0 : return true;
6216 : }
6217 :
6218 : static void
6219 0 : AddHyphenToMetrics(nsTextFrame* aTextFrame, const gfxTextRun* aBaseTextRun,
6220 : gfxTextRun::Metrics* aMetrics,
6221 : gfxFont::BoundingBoxType aBoundingBoxType,
6222 : DrawTarget* aDrawTarget)
6223 : {
6224 : // Fix up metrics to include hyphen
6225 : RefPtr<gfxTextRun> hyphenTextRun =
6226 0 : GetHyphenTextRun(aBaseTextRun, aDrawTarget, aTextFrame);
6227 0 : if (!hyphenTextRun) {
6228 0 : return;
6229 : }
6230 :
6231 : gfxTextRun::Metrics hyphenMetrics =
6232 0 : hyphenTextRun->MeasureText(aBoundingBoxType, aDrawTarget);
6233 0 : if (aTextFrame->GetWritingMode().IsLineInverted()) {
6234 0 : hyphenMetrics.mBoundingBox.y = -hyphenMetrics.mBoundingBox.YMost();
6235 : }
6236 0 : aMetrics->CombineWith(hyphenMetrics, aBaseTextRun->IsRightToLeft());
6237 : }
6238 :
6239 : void
6240 0 : nsTextFrame::PaintOneShadow(const PaintShadowParams& aParams,
6241 : nsCSSShadowItem* aShadowDetails,
6242 : gfxRect& aBoundingBox, uint32_t aBlurFlags)
6243 : {
6244 0 : AUTO_PROFILER_LABEL("nsTextFrame::PaintOneShadow", GRAPHICS);
6245 :
6246 0 : gfx::Point shadowOffset(aShadowDetails->mXOffset, aShadowDetails->mYOffset);
6247 0 : nscoord blurRadius = std::max(aShadowDetails->mRadius, 0);
6248 :
6249 0 : nscolor shadowColor = aShadowDetails->mHasColor ? aShadowDetails->mColor
6250 0 : : aParams.foregroundColor;
6251 :
6252 0 : if (auto* textDrawer = aParams.context->GetTextDrawer()) {
6253 : wr::Shadow wrShadow;
6254 :
6255 : wrShadow.offset = {
6256 0 : PresContext()->AppUnitsToFloatDevPixels(aShadowDetails->mXOffset),
6257 0 : PresContext()->AppUnitsToFloatDevPixels(aShadowDetails->mYOffset)
6258 0 : };
6259 :
6260 0 : wrShadow.blur_radius = PresContext()->AppUnitsToFloatDevPixels(blurRadius);
6261 0 : wrShadow.color = wr::ToColorF(ToDeviceColor(shadowColor));
6262 :
6263 0 : textDrawer->AppendShadow(wrShadow);
6264 : return;
6265 : }
6266 :
6267 : // This rect is the box which is equivalent to where the shadow will be painted.
6268 : // The origin of aBoundingBox is the text baseline left, so we must translate it by
6269 : // that much in order to make the origin the top-left corner of the text bounding box.
6270 : // Note that aLeftSideOffset is line-left, so actually means top offset in
6271 : // vertical writing modes.
6272 0 : gfxRect shadowGfxRect;
6273 0 : WritingMode wm = GetWritingMode();
6274 0 : if (wm.IsVertical()) {
6275 0 : shadowGfxRect = aBoundingBox;
6276 0 : if (wm.IsVerticalRL()) {
6277 : // for vertical-RL, reverse direction of x-coords of bounding box
6278 0 : shadowGfxRect.x = -shadowGfxRect.XMost();
6279 : }
6280 0 : shadowGfxRect += gfxPoint(aParams.textBaselinePt.x,
6281 0 : aParams.framePt.y + aParams.leftSideOffset);
6282 : } else {
6283 : shadowGfxRect =
6284 0 : aBoundingBox + gfxPoint(aParams.framePt.x + aParams.leftSideOffset,
6285 0 : aParams.textBaselinePt.y);
6286 : }
6287 0 : shadowGfxRect += gfxPoint(shadowOffset.x, shadowOffset.y);
6288 :
6289 : nsRect shadowRect(NSToCoordRound(shadowGfxRect.X()),
6290 : NSToCoordRound(shadowGfxRect.Y()),
6291 : NSToCoordRound(shadowGfxRect.Width()),
6292 0 : NSToCoordRound(shadowGfxRect.Height()));
6293 :
6294 0 : nsContextBoxBlur contextBoxBlur;
6295 0 : const auto A2D = PresContext()->AppUnitsPerDevPixel();
6296 : gfxContext* shadowContext = contextBoxBlur.Init(
6297 0 : shadowRect, 0, blurRadius, A2D, aParams.context,
6298 0 : LayoutDevicePixel::ToAppUnits(aParams.dirtyRect, A2D), nullptr, aBlurFlags);
6299 0 : if (!shadowContext)
6300 0 : return;
6301 :
6302 0 : aParams.context->Save();
6303 0 : aParams.context->SetColor(Color::FromABGR(shadowColor));
6304 :
6305 : // Draw the text onto our alpha-only surface to capture the alpha values.
6306 : // Remember that the box blur context has a device offset on it, so we don't need to
6307 : // translate any coordinates to fit on the surface.
6308 : gfxFloat advanceWidth;
6309 0 : nsTextPaintStyle textPaintStyle(this);
6310 0 : DrawTextParams params(shadowContext);
6311 0 : params.advanceWidth = &advanceWidth;
6312 0 : params.dirtyRect = aParams.dirtyRect;
6313 0 : params.framePt = aParams.framePt + shadowOffset;
6314 0 : params.provider = aParams.provider;
6315 0 : params.textStyle = &textPaintStyle;
6316 0 : params.textColor =
6317 0 : aParams.context == shadowContext ? shadowColor : NS_RGB(0, 0, 0);
6318 0 : params.clipEdges = aParams.clipEdges;
6319 0 : params.drawSoftHyphen = (GetStateBits() & TEXT_HYPHEN_BREAK) != 0;
6320 : // Multi-color shadow is not allowed, so we use the same color of the text color.
6321 0 : params.decorationOverrideColor = ¶ms.textColor;
6322 0 : DrawText(aParams.range, aParams.textBaselinePt + shadowOffset, params);
6323 :
6324 0 : contextBoxBlur.DoPaint();
6325 0 : aParams.context->Restore();
6326 : }
6327 :
6328 : // Paints selection backgrounds and text in the correct colors. Also computes
6329 : // aAllTypes, the union of all selection types that are applying to this text.
6330 : bool
6331 0 : nsTextFrame::PaintTextWithSelectionColors(
6332 : const PaintTextSelectionParams& aParams,
6333 : const UniquePtr<SelectionDetails>& aDetails,
6334 : SelectionTypeMask* aAllSelectionTypeMask,
6335 : const nsCharClipDisplayItem::ClipEdges& aClipEdges)
6336 : {
6337 0 : const gfxTextRun::Range& contentRange = aParams.contentRange;
6338 :
6339 : // Figure out which selections control the colors to use for each character.
6340 : // Note: prevailingSelectionsBuffer is keeping extra raw pointers to
6341 : // uniquely-owned resources, but it's safe because it's temporary and the
6342 : // resources are owned by the caller. Therefore, they'll outlive this object.
6343 0 : AutoTArray<SelectionDetails*,BIG_TEXT_NODE_SIZE> prevailingSelectionsBuffer;
6344 : SelectionDetails** prevailingSelections =
6345 0 : prevailingSelectionsBuffer.AppendElements(contentRange.Length(), fallible);
6346 0 : if (!prevailingSelections) {
6347 : return false;
6348 : }
6349 :
6350 : SelectionTypeMask allSelectionTypeMask = 0;
6351 0 : for (uint32_t i = 0; i < contentRange.Length(); ++i) {
6352 0 : prevailingSelections[i] = nullptr;
6353 : }
6354 :
6355 0 : bool anyBackgrounds = false;
6356 0 : for (SelectionDetails* sdptr = aDetails.get(); sdptr; sdptr = sdptr->mNext.get()) {
6357 0 : int32_t start = std::max(0, sdptr->mStart - int32_t(contentRange.start));
6358 0 : int32_t end = std::min(int32_t(contentRange.Length()),
6359 0 : sdptr->mEnd - int32_t(contentRange.start));
6360 0 : SelectionType selectionType = sdptr->mSelectionType;
6361 0 : if (start < end) {
6362 0 : allSelectionTypeMask |= ToSelectionTypeMask(selectionType);
6363 : // Ignore selections that don't set colors
6364 : nscolor foreground, background;
6365 0 : if (GetSelectionTextColors(selectionType, *aParams.textPaintStyle,
6366 : sdptr->mTextRangeStyle,
6367 : &foreground, &background)) {
6368 0 : if (NS_GET_A(background) > 0) {
6369 0 : anyBackgrounds = true;
6370 : }
6371 0 : for (int32_t i = start; i < end; ++i) {
6372 : // Favour normal selection over IME selections
6373 0 : if (!prevailingSelections[i] ||
6374 0 : selectionType < prevailingSelections[i]->mSelectionType) {
6375 0 : prevailingSelections[i] = sdptr;
6376 : }
6377 : }
6378 : }
6379 : }
6380 : }
6381 0 : *aAllSelectionTypeMask = allSelectionTypeMask;
6382 :
6383 0 : if (!allSelectionTypeMask) {
6384 : // Nothing is selected in the given text range. XXX can this still occur?
6385 : return false;
6386 : }
6387 :
6388 0 : bool vertical = mTextRun->IsVertical();
6389 0 : const gfxFloat startIOffset = vertical ?
6390 0 : aParams.textBaselinePt.y - aParams.framePt.y :
6391 0 : aParams.textBaselinePt.x - aParams.framePt.x;
6392 : gfxFloat iOffset, hyphenWidth;
6393 0 : Range range; // in transformed string
6394 0 : TextRangeStyle rangeStyle;
6395 : // Draw background colors
6396 :
6397 0 : auto* textDrawer = aParams.context->GetTextDrawer();
6398 :
6399 0 : if (anyBackgrounds && !aParams.IsGenerateTextMask()) {
6400 : int32_t appUnitsPerDevPixel =
6401 0 : aParams.textPaintStyle->PresContext()->AppUnitsPerDevPixel();
6402 : SelectionIterator iterator(prevailingSelections, contentRange,
6403 0 : *aParams.provider, mTextRun, startIOffset);
6404 : SelectionType selectionType;
6405 0 : while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth,
6406 : &selectionType, &rangeStyle)) {
6407 : nscolor foreground, background;
6408 0 : GetSelectionTextColors(selectionType, *aParams.textPaintStyle,
6409 0 : rangeStyle, &foreground, &background);
6410 : // Draw background color
6411 0 : gfxFloat advance = hyphenWidth +
6412 0 : mTextRun->GetAdvanceWidth(range, aParams.provider);
6413 0 : if (NS_GET_A(background) > 0) {
6414 0 : nsRect bgRect;
6415 0 : gfxFloat offs = iOffset - (mTextRun->IsInlineReversed() ? advance : 0);
6416 0 : if (vertical) {
6417 0 : bgRect = nsRect(aParams.framePt.x, aParams.framePt.y + offs,
6418 0 : GetSize().width, advance);
6419 : } else {
6420 0 : bgRect = nsRect(aParams.framePt.x + offs, aParams.framePt.y,
6421 0 : advance, GetSize().height);
6422 : }
6423 :
6424 : LayoutDeviceRect selectionRect =
6425 0 : LayoutDeviceRect::FromAppUnits(bgRect, appUnitsPerDevPixel);
6426 :
6427 0 : if (textDrawer) {
6428 0 : textDrawer->AppendSelectionRect(selectionRect, ToDeviceColor(background));
6429 : } else {
6430 0 : PaintSelectionBackground(
6431 0 : *aParams.context->GetDrawTarget(), background, aParams.dirtyRect,
6432 0 : selectionRect, aParams.callbacks);
6433 : }
6434 : }
6435 0 : iterator.UpdateWithAdvance(advance);
6436 : }
6437 : }
6438 :
6439 0 : if (aParams.IsPaintBGColor()) {
6440 : return true;
6441 : }
6442 :
6443 : gfxFloat advance;
6444 0 : DrawTextParams params(aParams.context);
6445 0 : params.dirtyRect = aParams.dirtyRect;
6446 0 : params.framePt = aParams.framePt;
6447 0 : params.provider = aParams.provider;
6448 0 : params.textStyle = aParams.textPaintStyle;
6449 0 : params.clipEdges = &aClipEdges;
6450 0 : params.advanceWidth = &advance;
6451 0 : params.callbacks = aParams.callbacks;
6452 :
6453 0 : PaintShadowParams shadowParams(aParams);
6454 0 : shadowParams.provider = aParams.provider;
6455 0 : shadowParams.clipEdges = &aClipEdges;
6456 :
6457 : // Draw text
6458 0 : const nsStyleText* textStyle = StyleText();
6459 : SelectionIterator iterator(prevailingSelections, contentRange,
6460 0 : *aParams.provider, mTextRun, startIOffset);
6461 : SelectionType selectionType;
6462 0 : while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth,
6463 : &selectionType, &rangeStyle)) {
6464 : nscolor foreground, background;
6465 0 : if (aParams.IsGenerateTextMask()) {
6466 0 : foreground = NS_RGBA(0, 0, 0, 255);
6467 : } else {
6468 0 : GetSelectionTextColors(selectionType, *aParams.textPaintStyle,
6469 0 : rangeStyle, &foreground, &background);
6470 : }
6471 :
6472 : gfx::Point textBaselinePt = vertical ?
6473 0 : gfx::Point(aParams.textBaselinePt.x, aParams.framePt.y + iOffset) :
6474 0 : gfx::Point(aParams.framePt.x + iOffset, aParams.textBaselinePt.y);
6475 :
6476 : // Determine what shadow, if any, to draw - either from textStyle
6477 : // or from the ::-moz-selection pseudo-class if specified there
6478 0 : nsCSSShadowArray* shadow = textStyle->GetTextShadow();
6479 0 : GetSelectionTextShadow(this, selectionType, *aParams.textPaintStyle,
6480 0 : &shadow);
6481 0 : if (shadow) {
6482 0 : nscoord startEdge = iOffset;
6483 0 : if (mTextRun->IsInlineReversed()) {
6484 0 : startEdge -= hyphenWidth +
6485 0 : mTextRun->GetAdvanceWidth(range, aParams.provider);
6486 : }
6487 0 : shadowParams.range = range;
6488 0 : shadowParams.textBaselinePt = textBaselinePt;
6489 0 : shadowParams.foregroundColor = foreground;
6490 0 : shadowParams.leftSideOffset = startEdge;
6491 0 : PaintShadows(shadow, shadowParams);
6492 : }
6493 :
6494 : // Draw text segment
6495 0 : params.textColor = foreground;
6496 0 : params.textStrokeColor = aParams.textPaintStyle->GetWebkitTextStrokeColor();
6497 0 : params.textStrokeWidth = aParams.textPaintStyle->GetWebkitTextStrokeWidth();
6498 0 : params.drawSoftHyphen = hyphenWidth > 0;
6499 0 : DrawText(range, textBaselinePt, params);
6500 0 : advance += hyphenWidth;
6501 0 : iterator.UpdateWithAdvance(advance);
6502 : }
6503 0 : return true;
6504 : }
6505 :
6506 : void
6507 0 : nsTextFrame::PaintTextSelectionDecorations(
6508 : const PaintTextSelectionParams& aParams,
6509 : const UniquePtr<SelectionDetails>& aDetails,
6510 : SelectionType aSelectionType)
6511 : {
6512 : // Hide text decorations if we're currently hiding @font-face fallback text
6513 0 : if (aParams.provider->GetFontGroup()->ShouldSkipDrawing())
6514 0 : return;
6515 :
6516 : // Figure out which characters will be decorated for this selection.
6517 : // Note: selectedCharsBuffer is keeping extra raw pointers to
6518 : // uniquely-owned resources, but it's safe because it's temporary and the
6519 : // resources are owned by the caller. Therefore, they'll outlive this object.
6520 0 : const gfxTextRun::Range& contentRange = aParams.contentRange;
6521 0 : AutoTArray<SelectionDetails*, BIG_TEXT_NODE_SIZE> selectedCharsBuffer;
6522 : SelectionDetails** selectedChars =
6523 0 : selectedCharsBuffer.AppendElements(contentRange.Length(), fallible);
6524 0 : if (!selectedChars) {
6525 : return;
6526 : }
6527 0 : for (uint32_t i = 0; i < contentRange.Length(); ++i) {
6528 0 : selectedChars[i] = nullptr;
6529 : }
6530 :
6531 0 : for (SelectionDetails* sdptr = aDetails.get(); sdptr; sdptr = sdptr->mNext.get()) {
6532 0 : if (sdptr->mSelectionType == aSelectionType) {
6533 0 : int32_t start = std::max(0, sdptr->mStart - int32_t(contentRange.start));
6534 0 : int32_t end = std::min(int32_t(contentRange.Length()),
6535 0 : sdptr->mEnd - int32_t(contentRange.start));
6536 0 : for (int32_t i = start; i < end; ++i) {
6537 0 : selectedChars[i] = sdptr;
6538 : }
6539 : }
6540 : }
6541 :
6542 0 : gfxFont* firstFont = aParams.provider->GetFontGroup()->GetFirstValidFont();
6543 0 : bool verticalRun = mTextRun->IsVertical();
6544 0 : bool rightUnderline = verticalRun && IsUnderlineRight(this);
6545 : const uint8_t kDecoration =
6546 : rightUnderline ? NS_STYLE_TEXT_DECORATION_LINE_OVERLINE :
6547 0 : NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE;
6548 0 : bool useVerticalMetrics = verticalRun && mTextRun->UseCenterBaseline();
6549 : gfxFont::Metrics
6550 : decorationMetrics(firstFont->GetMetrics(useVerticalMetrics ?
6551 0 : gfxFont::eVertical : gfxFont::eHorizontal));
6552 0 : if (!useVerticalMetrics) {
6553 : // The potential adjustment from using gfxFontGroup::GetUnderlineOffset
6554 : // is only valid for horizontal font metrics.
6555 0 : decorationMetrics.underlineOffset =
6556 0 : aParams.provider->GetFontGroup()->GetUnderlineOffset();
6557 : }
6558 :
6559 0 : gfxFloat startIOffset = verticalRun ?
6560 0 : aParams.textBaselinePt.y - aParams.framePt.y :
6561 0 : aParams.textBaselinePt.x - aParams.framePt.x;
6562 : SelectionIterator iterator(selectedChars, contentRange,
6563 0 : *aParams.provider, mTextRun, startIOffset);
6564 : gfxFloat iOffset, hyphenWidth;
6565 0 : Range range;
6566 0 : int32_t app = aParams.textPaintStyle->PresContext()->AppUnitsPerDevPixel();
6567 : // XXX aTextBaselinePt is in AppUnits, shouldn't it be nsFloatPoint?
6568 0 : Point pt;
6569 0 : if (verticalRun) {
6570 0 : pt.x = (aParams.textBaselinePt.x - mAscent) / app;
6571 : } else {
6572 0 : pt.y = (aParams.textBaselinePt.y - mAscent) / app;
6573 : }
6574 : SelectionType nextSelectionType;
6575 : TextRangeStyle selectedStyle;
6576 :
6577 0 : while (iterator.GetNextSegment(&iOffset, &range, &hyphenWidth,
6578 : &nextSelectionType, &selectedStyle)) {
6579 0 : gfxFloat advance = hyphenWidth +
6580 0 : mTextRun->GetAdvanceWidth(range, aParams.provider);
6581 0 : if (nextSelectionType == aSelectionType) {
6582 0 : if (verticalRun) {
6583 0 : pt.y = (aParams.framePt.y + iOffset -
6584 0 : (mTextRun->IsInlineReversed() ? advance : 0)) / app;
6585 : } else {
6586 0 : pt.x = (aParams.framePt.x + iOffset -
6587 0 : (mTextRun->IsInlineReversed() ? advance : 0)) / app;
6588 : }
6589 0 : gfxFloat width = Abs(advance) / app;
6590 0 : gfxFloat xInFrame = pt.x - (aParams.framePt.x / app);
6591 0 : DrawSelectionDecorations(
6592 0 : aParams.context, aParams.dirtyRect, aSelectionType,
6593 0 : *aParams.textPaintStyle, selectedStyle, pt, xInFrame,
6594 0 : width, mAscent / app, decorationMetrics, aParams.callbacks,
6595 0 : verticalRun, kDecoration);
6596 : }
6597 0 : iterator.UpdateWithAdvance(advance);
6598 : }
6599 : }
6600 :
6601 : bool
6602 0 : nsTextFrame::PaintTextWithSelection(
6603 : const PaintTextSelectionParams& aParams,
6604 : const nsCharClipDisplayItem::ClipEdges& aClipEdges)
6605 : {
6606 0 : NS_ASSERTION(GetContent()->IsSelectionDescendant(), "wrong paint path");
6607 :
6608 0 : UniquePtr<SelectionDetails> details = GetSelectionDetails();
6609 0 : if (!details) {
6610 : return false;
6611 : }
6612 :
6613 : SelectionTypeMask allSelectionTypeMask;
6614 0 : if (!PaintTextWithSelectionColors(aParams, details, &allSelectionTypeMask,
6615 : aClipEdges)) {
6616 : return false;
6617 : }
6618 : // Iterate through just the selection rawSelectionTypes that paint decorations
6619 : // and paint decorations for any that actually occur in this frame. Paint
6620 : // higher-numbered selection rawSelectionTypes below lower-numered ones on the
6621 : // general principal that lower-numbered selections are higher priority.
6622 0 : allSelectionTypeMask &= kSelectionTypesWithDecorations;
6623 : MOZ_ASSERT(kPresentSelectionTypes[0] == SelectionType::eNormal,
6624 : "The following for loop assumes that the first item of "
6625 : "kPresentSelectionTypes is SelectionType::eNormal");
6626 0 : for (size_t i = ArrayLength(kPresentSelectionTypes) - 1; i >= 1; --i) {
6627 0 : SelectionType selectionType = kPresentSelectionTypes[i];
6628 0 : if (ToSelectionTypeMask(selectionType) & allSelectionTypeMask) {
6629 : // There is some selection of this selectionType. Try to paint its
6630 : // decorations (there might not be any for this type but that's OK,
6631 : // PaintTextSelectionDecorations will exit early).
6632 0 : PaintTextSelectionDecorations(aParams, details, selectionType);
6633 : }
6634 : }
6635 :
6636 : return true;
6637 : }
6638 :
6639 : void
6640 0 : nsTextFrame::DrawEmphasisMarks(gfxContext* aContext,
6641 : WritingMode aWM,
6642 : const gfx::Point& aTextBaselinePt,
6643 : const gfx::Point& aFramePt, Range aRange,
6644 : const nscolor* aDecorationOverrideColor,
6645 : PropertyProvider* aProvider)
6646 : {
6647 0 : const EmphasisMarkInfo* info = GetProperty(EmphasisMarkProperty());
6648 0 : if (!info) {
6649 : return;
6650 : }
6651 :
6652 0 : bool isTextCombined = Style()->IsTextCombined();
6653 0 : nscolor color = aDecorationOverrideColor ? *aDecorationOverrideColor :
6654 0 : nsLayoutUtils::GetColor(this, &nsStyleText::mTextEmphasisColor);
6655 0 : aContext->SetColor(Color::FromABGR(color));
6656 0 : gfx::Point pt;
6657 0 : if (!isTextCombined) {
6658 0 : pt = aTextBaselinePt;
6659 : } else {
6660 0 : MOZ_ASSERT(aWM.IsVertical());
6661 0 : pt = aFramePt;
6662 0 : if (aWM.IsVerticalRL()) {
6663 0 : pt.x += GetSize().width - GetLogicalBaseline(aWM);
6664 : } else {
6665 0 : pt.x += GetLogicalBaseline(aWM);
6666 : }
6667 : }
6668 0 : if (!aWM.IsVertical()) {
6669 0 : pt.y += info->baselineOffset;
6670 : } else {
6671 0 : if (aWM.IsVerticalRL()) {
6672 0 : pt.x -= info->baselineOffset;
6673 : } else {
6674 0 : pt.x += info->baselineOffset;
6675 : }
6676 : }
6677 0 : if (!isTextCombined) {
6678 0 : mTextRun->DrawEmphasisMarks(aContext, info->textRun.get(),
6679 0 : info->advance, pt, aRange, aProvider);
6680 : } else {
6681 0 : pt.y += (GetSize().height - info->advance) / 2;
6682 0 : gfxTextRun::DrawParams params(aContext);
6683 0 : info->textRun->Draw(Range(info->textRun.get()), pt,
6684 0 : params);
6685 : }
6686 : }
6687 :
6688 : nscolor
6689 0 : nsTextFrame::GetCaretColorAt(int32_t aOffset)
6690 : {
6691 0 : MOZ_ASSERT(aOffset >= 0, "aOffset must be positive");
6692 :
6693 0 : nscolor result = nsFrame::GetCaretColorAt(aOffset);
6694 0 : gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
6695 0 : PropertyProvider provider(this, iter, nsTextFrame::eInflated);
6696 0 : int32_t contentOffset = provider.GetStart().GetOriginalOffset();
6697 0 : int32_t contentLength = provider.GetOriginalLength();
6698 0 : MOZ_ASSERT(aOffset >= contentOffset &&
6699 : aOffset <= contentOffset + contentLength,
6700 : "aOffset must be in the frame's range");
6701 :
6702 0 : int32_t offsetInFrame = aOffset - contentOffset;
6703 0 : if (offsetInFrame < 0 || offsetInFrame >= contentLength) {
6704 : return result;
6705 : }
6706 :
6707 0 : bool isSolidTextColor = true;
6708 0 : if (nsSVGUtils::IsInSVGTextSubtree(this)) {
6709 0 : const nsStyleSVG* style = StyleSVG();
6710 0 : if (style->mFill.Type() != eStyleSVGPaintType_None &&
6711 0 : style->mFill.Type() != eStyleSVGPaintType_Color) {
6712 0 : isSolidTextColor = false;
6713 : }
6714 : }
6715 :
6716 0 : nsTextPaintStyle textPaintStyle(this);
6717 0 : textPaintStyle.SetResolveColors(isSolidTextColor);
6718 0 : UniquePtr<SelectionDetails> details = GetSelectionDetails();
6719 0 : SelectionType selectionType = SelectionType::eNone;
6720 0 : for (SelectionDetails* sdptr = details.get(); sdptr; sdptr = sdptr->mNext.get()) {
6721 0 : int32_t start = std::max(0, sdptr->mStart - contentOffset);
6722 0 : int32_t end = std::min(contentLength, sdptr->mEnd - contentOffset);
6723 0 : if (start <= offsetInFrame && offsetInFrame < end &&
6724 0 : (selectionType == SelectionType::eNone ||
6725 0 : sdptr->mSelectionType < selectionType)) {
6726 : nscolor foreground, background;
6727 0 : if (GetSelectionTextColors(sdptr->mSelectionType, textPaintStyle,
6728 : sdptr->mTextRangeStyle,
6729 : &foreground, &background)) {
6730 0 : if (!isSolidTextColor &&
6731 0 : NS_IS_SELECTION_SPECIAL_COLOR(foreground)) {
6732 : result = NS_RGBA(0, 0, 0, 255);
6733 : } else {
6734 0 : result = foreground;
6735 : }
6736 0 : selectionType = sdptr->mSelectionType;
6737 : }
6738 : }
6739 : }
6740 :
6741 : return result;
6742 : }
6743 :
6744 : static gfxTextRun::Range
6745 0 : ComputeTransformedRange(PropertyProvider& aProvider)
6746 : {
6747 0 : gfxSkipCharsIterator iter(aProvider.GetStart());
6748 0 : uint32_t start = iter.GetSkippedOffset();
6749 0 : iter.AdvanceOriginal(aProvider.GetOriginalLength());
6750 0 : return gfxTextRun::Range(start, iter.GetSkippedOffset());
6751 : }
6752 :
6753 : bool
6754 0 : nsTextFrame::MeasureCharClippedText(nscoord aVisIStartEdge,
6755 : nscoord aVisIEndEdge,
6756 : nscoord* aSnappedStartEdge,
6757 : nscoord* aSnappedEndEdge)
6758 : {
6759 : // We need a *reference* rendering context (not one that might have a
6760 : // transform), so we don't have a rendering context argument.
6761 : // XXX get the block and line passed to us somehow! This is slow!
6762 0 : gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
6763 0 : if (!mTextRun)
6764 : return false;
6765 :
6766 0 : PropertyProvider provider(this, iter, nsTextFrame::eInflated);
6767 : // Trim trailing whitespace
6768 0 : provider.InitializeForDisplay(true);
6769 :
6770 0 : Range range = ComputeTransformedRange(provider);
6771 0 : uint32_t startOffset = range.start;
6772 0 : uint32_t maxLength = range.Length();
6773 : return MeasureCharClippedText(provider, aVisIStartEdge, aVisIEndEdge,
6774 : &startOffset, &maxLength,
6775 0 : aSnappedStartEdge, aSnappedEndEdge);
6776 : }
6777 :
6778 0 : static uint32_t GetClusterLength(const gfxTextRun* aTextRun,
6779 : uint32_t aStartOffset,
6780 : uint32_t aMaxLength,
6781 : bool aIsRTL)
6782 : {
6783 0 : uint32_t clusterLength = aIsRTL ? 0 : 1;
6784 0 : while (clusterLength < aMaxLength) {
6785 0 : if (aTextRun->IsClusterStart(aStartOffset + clusterLength)) {
6786 0 : if (aIsRTL) {
6787 0 : ++clusterLength;
6788 : }
6789 : break;
6790 : }
6791 0 : ++clusterLength;
6792 : }
6793 0 : return clusterLength;
6794 : }
6795 :
6796 : bool
6797 0 : nsTextFrame::MeasureCharClippedText(PropertyProvider& aProvider,
6798 : nscoord aVisIStartEdge,
6799 : nscoord aVisIEndEdge,
6800 : uint32_t* aStartOffset,
6801 : uint32_t* aMaxLength,
6802 : nscoord* aSnappedStartEdge,
6803 : nscoord* aSnappedEndEdge)
6804 : {
6805 0 : *aSnappedStartEdge = 0;
6806 0 : *aSnappedEndEdge = 0;
6807 0 : if (aVisIStartEdge <= 0 && aVisIEndEdge <= 0) {
6808 : return true;
6809 : }
6810 :
6811 0 : uint32_t offset = *aStartOffset;
6812 0 : uint32_t maxLength = *aMaxLength;
6813 0 : const nscoord frameISize = ISize();
6814 0 : const bool rtl = mTextRun->IsRightToLeft();
6815 0 : gfxFloat advanceWidth = 0;
6816 0 : const nscoord startEdge = rtl ? aVisIEndEdge : aVisIStartEdge;
6817 0 : if (startEdge > 0) {
6818 0 : const gfxFloat maxAdvance = gfxFloat(startEdge);
6819 0 : while (maxLength > 0) {
6820 : uint32_t clusterLength =
6821 0 : GetClusterLength(mTextRun, offset, maxLength, rtl);
6822 0 : advanceWidth += mTextRun->
6823 0 : GetAdvanceWidth(Range(offset, offset + clusterLength), &aProvider);
6824 0 : maxLength -= clusterLength;
6825 0 : offset += clusterLength;
6826 0 : if (advanceWidth >= maxAdvance) {
6827 : break;
6828 : }
6829 : }
6830 0 : nscoord* snappedStartEdge = rtl ? aSnappedEndEdge : aSnappedStartEdge;
6831 0 : *snappedStartEdge = NSToCoordFloor(advanceWidth);
6832 0 : *aStartOffset = offset;
6833 : }
6834 :
6835 0 : const nscoord endEdge = rtl ? aVisIStartEdge : aVisIEndEdge;
6836 0 : if (endEdge > 0) {
6837 0 : const gfxFloat maxAdvance = gfxFloat(frameISize - endEdge);
6838 0 : while (maxLength > 0) {
6839 : uint32_t clusterLength =
6840 0 : GetClusterLength(mTextRun, offset, maxLength, rtl);
6841 0 : gfxFloat nextAdvance = advanceWidth + mTextRun->GetAdvanceWidth(
6842 0 : Range(offset, offset + clusterLength), &aProvider);
6843 0 : if (nextAdvance > maxAdvance) {
6844 : break;
6845 : }
6846 : // This cluster fits, include it.
6847 0 : advanceWidth = nextAdvance;
6848 0 : maxLength -= clusterLength;
6849 0 : offset += clusterLength;
6850 : }
6851 0 : maxLength = offset - *aStartOffset;
6852 0 : nscoord* snappedEndEdge = rtl ? aSnappedStartEdge : aSnappedEndEdge;
6853 0 : *snappedEndEdge = NSToCoordFloor(gfxFloat(frameISize) - advanceWidth);
6854 : }
6855 0 : *aMaxLength = maxLength;
6856 0 : return maxLength != 0;
6857 : }
6858 :
6859 : void
6860 0 : nsTextFrame::PaintShadows(nsCSSShadowArray* aShadow,
6861 : const PaintShadowParams& aParams)
6862 : {
6863 0 : if (!aShadow) {
6864 0 : return;
6865 : }
6866 :
6867 : gfxTextRun::Metrics shadowMetrics =
6868 : mTextRun->MeasureText(aParams.range, gfxFont::LOOSE_INK_EXTENTS,
6869 0 : nullptr, aParams.provider);
6870 0 : if (GetWritingMode().IsLineInverted()) {
6871 0 : Swap(shadowMetrics.mAscent, shadowMetrics.mDescent);
6872 0 : shadowMetrics.mBoundingBox.y = -shadowMetrics.mBoundingBox.YMost();
6873 : }
6874 0 : if (GetStateBits() & TEXT_HYPHEN_BREAK) {
6875 0 : AddHyphenToMetrics(this, mTextRun, &shadowMetrics,
6876 : gfxFont::LOOSE_INK_EXTENTS,
6877 0 : aParams.context->GetDrawTarget());
6878 : }
6879 : // Add bounds of text decorations
6880 : gfxRect decorationRect(0, -shadowMetrics.mAscent,
6881 0 : shadowMetrics.mAdvanceWidth, shadowMetrics.mAscent + shadowMetrics.mDescent);
6882 : shadowMetrics.mBoundingBox.UnionRect(shadowMetrics.mBoundingBox,
6883 0 : decorationRect);
6884 :
6885 : // If the textrun uses any color or SVG fonts, we need to force use of a mask
6886 : // for shadow rendering even if blur radius is zero.
6887 : // Force disable hardware acceleration for text shadows since it's usually
6888 : // more expensive than just doing it on the CPU.
6889 0 : uint32_t blurFlags = nsContextBoxBlur::DISABLE_HARDWARE_ACCELERATION_BLUR;
6890 : uint32_t numGlyphRuns;
6891 0 : const gfxTextRun::GlyphRun* run = mTextRun->GetGlyphRuns(&numGlyphRuns);
6892 0 : while (numGlyphRuns-- > 0) {
6893 0 : if (run->mFont->AlwaysNeedsMaskForShadow()) {
6894 : blurFlags |= nsContextBoxBlur::FORCE_MASK;
6895 : break;
6896 : }
6897 0 : run++;
6898 : }
6899 :
6900 0 : if (mTextRun->IsVertical()) {
6901 0 : Swap(shadowMetrics.mBoundingBox.x, shadowMetrics.mBoundingBox.y);
6902 : Swap(shadowMetrics.mBoundingBox.width, shadowMetrics.mBoundingBox.height);
6903 : }
6904 :
6905 0 : for (uint32_t i = aShadow->Length(); i > 0; --i) {
6906 0 : PaintOneShadow(aParams, aShadow->ShadowAt(i - 1),
6907 0 : shadowMetrics.mBoundingBox, blurFlags);
6908 : }
6909 : }
6910 :
6911 : static bool
6912 0 : ShouldDrawSelection(const nsIFrame* aFrame)
6913 : {
6914 : // Normal text-with-selection rendering sequence is:
6915 : // * Paint background > Paint text-selection-color > Paint text
6916 : // When we have an parent frame with background-clip-text style, rendering
6917 : // sequence changes to:
6918 : // * Paint text-selection-color > Paint background > Paint text
6919 : //
6920 : // If there is a parent frame has background-clip:text style,
6921 : // text-selection-color should be drawn with the background of that parent
6922 : // frame, so we should not draw it again while painting text frames.
6923 :
6924 0 : if (!aFrame) {
6925 : return true;
6926 : }
6927 :
6928 0 : const nsStyleBackground* bg = aFrame->Style()->StyleBackground();
6929 0 : const nsStyleImageLayers& layers = bg->mImage;
6930 0 : NS_FOR_VISIBLE_IMAGE_LAYERS_BACK_TO_FRONT(i, layers) {
6931 0 : if (layers.mLayers[i].mClip == StyleGeometryBox::Text) {
6932 : return false;
6933 : }
6934 : }
6935 :
6936 0 : return ShouldDrawSelection(aFrame->GetParent());
6937 : }
6938 :
6939 : void
6940 0 : nsTextFrame::PaintText(const PaintTextParams& aParams,
6941 : const nsCharClipDisplayItem& aItem,
6942 : float aOpacity /* = 1.0f */)
6943 : {
6944 : // Don't pass in the rendering context here, because we need a
6945 : // *reference* context and rendering context might have some transform
6946 : // in it
6947 : // XXX get the block and line passed to us somehow! This is slow!
6948 0 : gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
6949 0 : if (!mTextRun)
6950 0 : return;
6951 :
6952 0 : PropertyProvider provider(this, iter, nsTextFrame::eInflated);
6953 0 : if (aItem.mIsFrameSelected.isNothing()) {
6954 0 : aItem.mIsFrameSelected.emplace(IsSelected());
6955 : }
6956 : // Trim trailing whitespace, unless we're painting a selection highlight,
6957 : // which should include trailing spaces if present (bug 1146754).
6958 0 : provider.InitializeForDisplay(!aItem.mIsFrameSelected.value());
6959 :
6960 0 : const bool reversed = mTextRun->IsInlineReversed();
6961 0 : const bool verticalRun = mTextRun->IsVertical();
6962 0 : WritingMode wm = GetWritingMode();
6963 0 : const float frameWidth = GetSize().width;
6964 0 : const float frameHeight = GetSize().height;
6965 0 : gfx::Point textBaselinePt;
6966 0 : if (verticalRun) {
6967 0 : if (wm.IsVerticalLR()) {
6968 0 : textBaselinePt.x = nsLayoutUtils::GetSnappedBaselineX(
6969 0 : this, aParams.context, nscoord(aParams.framePt.x), mAscent);
6970 : } else {
6971 0 : textBaselinePt.x = nsLayoutUtils::GetSnappedBaselineX(
6972 0 : this, aParams.context, nscoord(aParams.framePt.x) + frameWidth,
6973 0 : -mAscent);
6974 : }
6975 0 : textBaselinePt.y = reversed ? aParams.framePt.y + frameHeight
6976 : : aParams.framePt.y;
6977 : } else {
6978 0 : textBaselinePt =
6979 0 : gfx::Point(reversed ? aParams.framePt.x + frameWidth : aParams.framePt.x,
6980 0 : nsLayoutUtils::GetSnappedBaselineY(
6981 0 : this, aParams.context, aParams.framePt.y, mAscent));
6982 : }
6983 0 : Range range = ComputeTransformedRange(provider);
6984 0 : uint32_t startOffset = range.start;
6985 0 : uint32_t maxLength = range.Length();
6986 : nscoord snappedStartEdge, snappedEndEdge;
6987 0 : if (!MeasureCharClippedText(provider, aItem.mVisIStartEdge, aItem.mVisIEndEdge,
6988 : &startOffset, &maxLength, &snappedStartEdge, &snappedEndEdge)) {
6989 0 : return;
6990 : }
6991 0 : if (verticalRun) {
6992 0 : textBaselinePt.y += reversed ? -snappedEndEdge : snappedStartEdge;
6993 : } else {
6994 0 : textBaselinePt.x += reversed ? -snappedEndEdge : snappedStartEdge;
6995 : }
6996 : nsCharClipDisplayItem::ClipEdges clipEdges(aItem, snappedStartEdge,
6997 0 : snappedEndEdge);
6998 0 : nsTextPaintStyle textPaintStyle(this);
6999 0 : textPaintStyle.SetResolveColors(!aParams.callbacks);
7000 :
7001 : // Fork off to the (slower) paint-with-selection path if necessary.
7002 0 : if (aItem.mIsFrameSelected.value() &&
7003 0 : (aParams.IsPaintBGColor() || ShouldDrawSelection(this->GetParent()))) {
7004 0 : MOZ_ASSERT(aOpacity == 1.0f, "We don't support opacity with selections!");
7005 0 : gfxSkipCharsIterator tmp(provider.GetStart());
7006 : Range contentRange(
7007 0 : uint32_t(tmp.ConvertSkippedToOriginal(startOffset)),
7008 0 : uint32_t(tmp.ConvertSkippedToOriginal(startOffset + maxLength)));
7009 0 : PaintTextSelectionParams params(aParams);
7010 0 : params.textBaselinePt = textBaselinePt;
7011 0 : params.provider = &provider;
7012 0 : params.contentRange = contentRange;
7013 0 : params.textPaintStyle = &textPaintStyle;
7014 0 : if (PaintTextWithSelection(params, clipEdges)) {
7015 0 : return;
7016 : }
7017 : }
7018 :
7019 0 : if (aParams.IsPaintBGColor()) {
7020 : return;
7021 : }
7022 :
7023 0 : nscolor foregroundColor = aParams.IsGenerateTextMask()
7024 0 : ? NS_RGBA(0, 0, 0, 255)
7025 0 : : textPaintStyle.GetTextColor();
7026 0 : if (aOpacity != 1.0f) {
7027 0 : gfx::Color gfxColor = gfx::Color::FromABGR(foregroundColor);
7028 0 : gfxColor.a *= aOpacity;
7029 0 : foregroundColor = gfxColor.ToABGR();
7030 : }
7031 :
7032 0 : nscolor textStrokeColor = aParams.IsGenerateTextMask()
7033 0 : ? NS_RGBA(0, 0, 0, 255)
7034 0 : : textPaintStyle.GetWebkitTextStrokeColor();
7035 0 : if (aOpacity != 1.0f) {
7036 0 : gfx::Color gfxColor = gfx::Color::FromABGR(textStrokeColor);
7037 0 : gfxColor.a *= aOpacity;
7038 0 : textStrokeColor = gfxColor.ToABGR();
7039 : }
7040 :
7041 0 : range = Range(startOffset, startOffset + maxLength);
7042 0 : if (!aParams.callbacks && aParams.IsPaintText()) {
7043 0 : const nsStyleText* textStyle = StyleText();
7044 0 : PaintShadowParams shadowParams(aParams);
7045 0 : shadowParams.range = range;
7046 0 : shadowParams.textBaselinePt = textBaselinePt;
7047 0 : shadowParams.leftSideOffset = snappedStartEdge;
7048 0 : shadowParams.provider = &provider;
7049 0 : shadowParams.foregroundColor = foregroundColor;
7050 0 : shadowParams.clipEdges = &clipEdges;
7051 0 : PaintShadows(textStyle->mTextShadow, shadowParams);
7052 : }
7053 :
7054 : gfxFloat advanceWidth;
7055 0 : DrawTextParams params(aParams.context);
7056 0 : params.dirtyRect = aParams.dirtyRect;
7057 0 : params.framePt = aParams.framePt;
7058 0 : params.provider = &provider;
7059 0 : params.advanceWidth = &advanceWidth;
7060 0 : params.textStyle = &textPaintStyle;
7061 0 : params.textColor = foregroundColor;
7062 0 : params.textStrokeColor = textStrokeColor;
7063 0 : params.textStrokeWidth = textPaintStyle.GetWebkitTextStrokeWidth();
7064 0 : params.clipEdges = &clipEdges;
7065 0 : params.drawSoftHyphen = (GetStateBits() & TEXT_HYPHEN_BREAK) != 0;
7066 0 : params.contextPaint = aParams.contextPaint;
7067 0 : params.callbacks = aParams.callbacks;
7068 0 : DrawText(range, textBaselinePt, params);
7069 : }
7070 :
7071 : static void
7072 0 : DrawTextRun(const gfxTextRun* aTextRun,
7073 : const gfx::Point& aTextBaselinePt,
7074 : gfxTextRun::Range aRange,
7075 : const nsTextFrame::DrawTextRunParams& aParams,
7076 : nsTextFrame* aFrame)
7077 : {
7078 0 : gfxTextRun::DrawParams params(aParams.context);
7079 0 : params.provider = aParams.provider;
7080 0 : params.advanceWidth = aParams.advanceWidth;
7081 0 : params.contextPaint = aParams.contextPaint;
7082 0 : params.callbacks = aParams.callbacks;
7083 0 : if (aParams.callbacks) {
7084 0 : aParams.callbacks->NotifyBeforeText(aParams.textColor);
7085 0 : params.drawMode = DrawMode::GLYPH_PATH;
7086 0 : aTextRun->Draw(aRange, aTextBaselinePt, params);
7087 0 : aParams.callbacks->NotifyAfterText();
7088 : } else {
7089 0 : auto* textDrawer = aParams.context->GetTextDrawer();
7090 0 : if (NS_GET_A(aParams.textColor) != 0 || textDrawer) {
7091 0 : aParams.context->SetColor(Color::FromABGR(aParams.textColor));
7092 : } else {
7093 0 : params.drawMode = DrawMode::GLYPH_STROKE;
7094 : }
7095 :
7096 0 : if ((NS_GET_A(aParams.textStrokeColor) != 0 || textDrawer) &&
7097 0 : aParams.textStrokeWidth != 0.0f) {
7098 0 : if (textDrawer) {
7099 0 : textDrawer->FoundUnsupportedFeature();
7100 0 : return;
7101 : }
7102 0 : params.drawMode |= DrawMode::GLYPH_STROKE;
7103 0 : if (gfxPrefs::PaintOrderEnabled()) {
7104 : // Check the paint-order property; if we find stroke before fill,
7105 : // then change mode to GLYPH_STROKE_UNDERNEATH.
7106 0 : uint32_t paintOrder = aFrame->StyleSVG()->mPaintOrder;
7107 0 : if (paintOrder != NS_STYLE_PAINT_ORDER_NORMAL) {
7108 0 : while (paintOrder) {
7109 : uint32_t component =
7110 0 : paintOrder & ((1 << NS_STYLE_PAINT_ORDER_BITWIDTH) - 1);
7111 0 : switch (component) {
7112 : case NS_STYLE_PAINT_ORDER_FILL:
7113 : // Just break the loop, no need to check further
7114 0 : paintOrder = 0;
7115 0 : break;
7116 : case NS_STYLE_PAINT_ORDER_STROKE:
7117 0 : params.drawMode |= DrawMode::GLYPH_STROKE_UNDERNEATH;
7118 0 : paintOrder = 0;
7119 0 : break;
7120 : }
7121 0 : paintOrder >>= NS_STYLE_PAINT_ORDER_BITWIDTH;
7122 : }
7123 : }
7124 : }
7125 0 : StrokeOptions strokeOpts;
7126 0 : params.textStrokeColor = aParams.textStrokeColor;
7127 0 : strokeOpts.mLineWidth = aParams.textStrokeWidth;
7128 0 : params.strokeOpts = &strokeOpts;
7129 0 : aTextRun->Draw(aRange, aTextBaselinePt, params);
7130 : } else {
7131 0 : aTextRun->Draw(aRange, aTextBaselinePt, params);
7132 : }
7133 : }
7134 : }
7135 :
7136 : void
7137 0 : nsTextFrame::DrawTextRun(Range aRange, const gfx::Point& aTextBaselinePt,
7138 : const DrawTextRunParams& aParams)
7139 : {
7140 0 : MOZ_ASSERT(aParams.advanceWidth, "Must provide advanceWidth");
7141 :
7142 0 : ::DrawTextRun(mTextRun, aTextBaselinePt, aRange, aParams, this);
7143 :
7144 0 : if (aParams.drawSoftHyphen) {
7145 : // Don't use ctx as the context, because we need a reference context here,
7146 : // ctx may be transformed.
7147 : RefPtr<gfxTextRun> hyphenTextRun =
7148 0 : GetHyphenTextRun(mTextRun, nullptr, this);
7149 0 : if (hyphenTextRun) {
7150 : // For right-to-left text runs, the soft-hyphen is positioned at the left
7151 : // of the text, minus its own width
7152 0 : float hyphenBaselineX = aTextBaselinePt.x +
7153 0 : mTextRun->GetDirection() * (*aParams.advanceWidth) -
7154 0 : (mTextRun->IsRightToLeft() ? hyphenTextRun->GetAdvanceWidth() : 0);
7155 0 : DrawTextRunParams params = aParams;
7156 0 : params.provider = nullptr;
7157 0 : params.advanceWidth = nullptr;
7158 0 : ::DrawTextRun(hyphenTextRun.get(),
7159 0 : gfx::Point(hyphenBaselineX, aTextBaselinePt.y),
7160 0 : Range(hyphenTextRun.get()), params, this);
7161 : }
7162 : }
7163 0 : }
7164 :
7165 : void
7166 0 : nsTextFrame::DrawTextRunAndDecorations(Range aRange,
7167 : const gfx::Point& aTextBaselinePt,
7168 : const DrawTextParams& aParams,
7169 : const TextDecorations& aDecorations)
7170 : {
7171 : const gfxFloat app =
7172 0 : aParams.textStyle->PresContext()->AppUnitsPerDevPixel();
7173 : // Writing mode of parent frame is used because the text frame may
7174 : // be orthogonal to its parent when text-combine-upright is used or
7175 : // its parent has "display: contents", and in those cases, we want
7176 : // to draw the decoration lines according to parents' direction
7177 : // rather than ours.
7178 0 : const WritingMode wm = GetParent()->GetWritingMode();
7179 0 : bool verticalDec = wm.IsVertical();
7180 0 : bool verticalRun = mTextRun->IsVertical();
7181 : // If the text run and the decoration is orthogonal, we choose the
7182 : // metrics for decoration so that decoration line won't be broken.
7183 : bool useVerticalMetrics = verticalDec != verticalRun
7184 0 : ? verticalDec : verticalRun && mTextRun->UseCenterBaseline();
7185 :
7186 : // XXX aFramePt is in AppUnits, shouldn't it be nsFloatPoint?
7187 0 : nscoord x = NSToCoordRound(aParams.framePt.x);
7188 0 : nscoord y = NSToCoordRound(aParams.framePt.y);
7189 :
7190 : // 'measure' here is textrun-relative, so for a horizontal run it's the
7191 : // width, while for a vertical run it's the height of the decoration
7192 0 : const nsSize frameSize = GetSize();
7193 0 : nscoord measure = verticalDec ? frameSize.height : frameSize.width;
7194 :
7195 0 : if (verticalDec) {
7196 0 : aParams.clipEdges->Intersect(&y, &measure);
7197 : } else {
7198 0 : aParams.clipEdges->Intersect(&x, &measure);
7199 : }
7200 :
7201 : // decSize is a textrun-relative size, so its 'width' field is actually
7202 : // the run-relative measure, and 'height' will be the line thickness
7203 0 : gfxFloat ascent = gfxFloat(GetLogicalBaseline(wm)) / app;
7204 : // The starting edge of the frame in block direction
7205 0 : gfxFloat frameBStart = verticalDec ? aParams.framePt.x : aParams.framePt.y;
7206 :
7207 : // In vertical-rl mode, block coordinates are measured from the
7208 : // right, so we need to adjust here.
7209 0 : if (wm.IsVerticalRL()) {
7210 0 : frameBStart += frameSize.width;
7211 0 : ascent = -ascent;
7212 : }
7213 :
7214 : nscoord inflationMinFontSize =
7215 0 : nsLayoutUtils::InflationMinFontSizeFor(this);
7216 :
7217 0 : PaintDecorationLineParams params;
7218 0 : params.context = aParams.context;
7219 0 : params.dirtyRect = aParams.dirtyRect;
7220 0 : params.overrideColor = aParams.decorationOverrideColor;
7221 0 : params.callbacks = aParams.callbacks;
7222 : // pt is the physical point where the decoration is to be drawn,
7223 : // relative to the frame; one of its coordinates will be updated below.
7224 0 : params.pt = Point(x / app, y / app);
7225 0 : Float& bCoord = verticalDec ? params.pt.x : params.pt.y;
7226 0 : params.lineSize = Size(measure / app, 0);
7227 0 : params.ascent = ascent;
7228 0 : params.vertical = verticalDec;
7229 0 : params.sidewaysLeft = mTextRun->IsSidewaysLeft();
7230 :
7231 : // The matrix of the context may have been altered for text-combine-
7232 : // upright. However, we want to draw decoration lines unscaled, thus
7233 : // we need to revert the scaling here.
7234 0 : gfxContextMatrixAutoSaveRestore scaledRestorer;
7235 0 : if (Style()->IsTextCombined()) {
7236 0 : float scaleFactor = GetTextCombineScaleFactor(this);
7237 0 : if (scaleFactor != 1.0f) {
7238 0 : scaledRestorer.SetContext(aParams.context);
7239 0 : gfxMatrix unscaled = aParams.context->CurrentMatrixDouble();
7240 0 : gfxPoint pt(x / app, y / app);
7241 0 : unscaled.PreTranslate(pt).PreScale(1.0f / scaleFactor, 1.0f).PreTranslate(-pt);
7242 0 : aParams.context->SetMatrixDouble(unscaled);
7243 : }
7244 : }
7245 :
7246 : typedef gfxFont::Metrics Metrics;
7247 : auto paintDecorationLine = [&](const LineDecoration& dec,
7248 : gfxFloat Metrics::* lineSize,
7249 0 : gfxFloat Metrics::* lineOffset) {
7250 0 : if (dec.mStyle == NS_STYLE_TEXT_DECORATION_STYLE_NONE) {
7251 0 : return;
7252 : }
7253 :
7254 : float inflation =
7255 0 : GetInflationForTextDecorations(dec.mFrame, inflationMinFontSize);
7256 : const Metrics metrics =
7257 0 : GetFirstFontMetrics(GetFontGroupForFrame(dec.mFrame, inflation),
7258 0 : useVerticalMetrics);
7259 :
7260 0 : params.lineSize.height = metrics.*lineSize;
7261 0 : bCoord = (frameBStart - dec.mBaselineOffset) / app;
7262 :
7263 0 : params.color = dec.mColor;
7264 0 : params.offset = metrics.*lineOffset;
7265 0 : params.style = dec.mStyle;
7266 0 : PaintDecorationLine(params);
7267 0 : };
7268 :
7269 : // We create a clip region in order to draw the decoration lines only in the
7270 : // range of the text. Restricting the draw area prevents the decoration lines
7271 : // to be drawn multiple times when a part of the text is selected.
7272 :
7273 : // We skip clipping for the following cases:
7274 : // - drawing the whole text
7275 : // - having different orientation of the text and the writing-mode, such as
7276 : // "text-combine-upright" (Bug 1408825)
7277 0 : bool skipClipping = aRange.Length() == mTextRun->GetLength() ||
7278 0 : verticalDec != verticalRun;
7279 :
7280 0 : gfxRect clipRect;
7281 0 : if (!skipClipping) {
7282 : // Get the inline-size according to the specified range.
7283 0 : gfxFloat clipLength = mTextRun->GetAdvanceWidth(aRange, aParams.provider);
7284 0 : nsRect visualRect = GetVisualOverflowRect();
7285 :
7286 0 : const bool isInlineReversed = mTextRun->IsInlineReversed();
7287 0 : if (verticalDec) {
7288 0 : clipRect.x = aParams.framePt.x + visualRect.x;
7289 0 : clipRect.y = isInlineReversed ? aTextBaselinePt.y - clipLength
7290 0 : : aTextBaselinePt.y;
7291 0 : clipRect.width = visualRect.width;
7292 0 : clipRect.height = clipLength;
7293 : } else {
7294 0 : clipRect.x = isInlineReversed ? aTextBaselinePt.x - clipLength
7295 0 : : aTextBaselinePt.x;
7296 0 : clipRect.y = aParams.framePt.y + visualRect.y;
7297 0 : clipRect.width = clipLength;
7298 0 : clipRect.height = visualRect.height;
7299 : }
7300 :
7301 0 : clipRect.Scale(1 / app);
7302 0 : clipRect.Round();
7303 0 : params.context->Clip(clipRect);
7304 : }
7305 :
7306 : // Underlines
7307 0 : params.decoration = NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE;
7308 0 : for (const LineDecoration& dec : Reversed(aDecorations.mUnderlines)) {
7309 : paintDecorationLine(dec, &Metrics::underlineSize,
7310 0 : &Metrics::underlineOffset);
7311 : }
7312 :
7313 : // Overlines
7314 0 : params.decoration = NS_STYLE_TEXT_DECORATION_LINE_OVERLINE;
7315 0 : for (const LineDecoration& dec : Reversed(aDecorations.mOverlines)) {
7316 0 : paintDecorationLine(dec, &Metrics::underlineSize, &Metrics::maxAscent);
7317 : }
7318 :
7319 : // Some glyphs and emphasis marks may extend outside the region, so we reset
7320 : // the clip region here. For an example, italic glyphs.
7321 0 : if (!skipClipping) {
7322 0 : params.context->PopClip();
7323 : }
7324 :
7325 : {
7326 0 : gfxContextMatrixAutoSaveRestore unscaledRestorer;
7327 0 : if (scaledRestorer.HasMatrix()) {
7328 0 : unscaledRestorer.SetContext(aParams.context);
7329 0 : aParams.context->SetMatrix(scaledRestorer.Matrix());
7330 : }
7331 :
7332 : // CSS 2.1 mandates that text be painted after over/underlines,
7333 : // and *then* line-throughs
7334 0 : DrawTextRun(aRange, aTextBaselinePt, aParams);
7335 : }
7336 :
7337 : // Emphasis marks
7338 0 : DrawEmphasisMarks(aParams.context, wm,
7339 : aTextBaselinePt, aParams.framePt, aRange,
7340 0 : aParams.decorationOverrideColor, aParams.provider);
7341 :
7342 : // Re-apply the clip region when the line-through is being drawn.
7343 0 : if (!skipClipping) {
7344 0 : params.context->Clip(clipRect);
7345 : }
7346 :
7347 : // Line-throughs
7348 0 : params.decoration = NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH;
7349 0 : for (const LineDecoration& dec : Reversed(aDecorations.mStrikes)) {
7350 : paintDecorationLine(dec, &Metrics::strikeoutSize,
7351 0 : &Metrics::strikeoutOffset);
7352 : }
7353 :
7354 0 : if (!skipClipping) {
7355 0 : params.context->PopClip();
7356 : }
7357 0 : }
7358 :
7359 : void
7360 0 : nsTextFrame::DrawText(Range aRange, const gfx::Point& aTextBaselinePt,
7361 : const DrawTextParams& aParams)
7362 : {
7363 0 : TextDecorations decorations;
7364 0 : GetTextDecorations(aParams.textStyle->PresContext(),
7365 0 : aParams.callbacks ? eUnresolvedColors : eResolvedColors,
7366 0 : decorations);
7367 :
7368 : // Hide text decorations if we're currently hiding @font-face fallback text
7369 : const bool drawDecorations =
7370 0 : !aParams.provider->GetFontGroup()->ShouldSkipDrawing() &&
7371 0 : (decorations.HasDecorationLines() || StyleText()->HasTextEmphasis());
7372 0 : if (drawDecorations) {
7373 0 : DrawTextRunAndDecorations(aRange, aTextBaselinePt, aParams, decorations);
7374 : } else {
7375 0 : DrawTextRun(aRange, aTextBaselinePt, aParams);
7376 : }
7377 :
7378 0 : if (auto* textDrawer = aParams.context->GetTextDrawer()) {
7379 0 : textDrawer->TerminateShadows();
7380 : }
7381 0 : }
7382 :
7383 : int16_t
7384 0 : nsTextFrame::GetSelectionStatus(int16_t* aSelectionFlags)
7385 : {
7386 : // get the selection controller
7387 0 : nsCOMPtr<nsISelectionController> selectionController;
7388 0 : nsresult rv = GetSelectionController(PresContext(),
7389 0 : getter_AddRefs(selectionController));
7390 0 : if (NS_FAILED(rv) || !selectionController)
7391 : return nsISelectionController::SELECTION_OFF;
7392 :
7393 0 : selectionController->GetSelectionFlags(aSelectionFlags);
7394 :
7395 : int16_t selectionValue;
7396 0 : selectionController->GetDisplaySelection(&selectionValue);
7397 :
7398 0 : return selectionValue;
7399 : }
7400 :
7401 : bool
7402 0 : nsTextFrame::IsVisibleInSelection(Selection* aSelection)
7403 : {
7404 : // Check the quick way first
7405 0 : if (!GetContent()->IsSelectionDescendant())
7406 : return false;
7407 :
7408 0 : UniquePtr<SelectionDetails> details = GetSelectionDetails();
7409 0 : bool found = false;
7410 :
7411 : // where are the selection points "really"
7412 0 : for (SelectionDetails* sdptr = details.get(); sdptr; sdptr = sdptr->mNext.get()) {
7413 0 : if (sdptr->mEnd > GetContentOffset() &&
7414 0 : sdptr->mStart < GetContentEnd() &&
7415 0 : sdptr->mSelectionType == SelectionType::eNormal) {
7416 : found = true;
7417 : break;
7418 : }
7419 : }
7420 :
7421 : return found;
7422 : }
7423 :
7424 : /**
7425 : * Compute the longest prefix of text whose width is <= aWidth. Return
7426 : * the length of the prefix. Also returns the width of the prefix in aFitWidth.
7427 : */
7428 : static uint32_t
7429 0 : CountCharsFit(const gfxTextRun* aTextRun, gfxTextRun::Range aRange,
7430 : gfxFloat aWidth, PropertyProvider* aProvider,
7431 : gfxFloat* aFitWidth)
7432 : {
7433 0 : uint32_t last = 0;
7434 0 : gfxFloat width = 0;
7435 0 : for (uint32_t i = 1; i <= aRange.Length(); ++i) {
7436 0 : if (i == aRange.Length() || aTextRun->IsClusterStart(aRange.start + i)) {
7437 0 : gfxTextRun::Range range(aRange.start + last, aRange.start + i);
7438 0 : gfxFloat nextWidth = width + aTextRun->GetAdvanceWidth(range, aProvider);
7439 0 : if (nextWidth > aWidth)
7440 : break;
7441 0 : last = i;
7442 0 : width = nextWidth;
7443 : }
7444 : }
7445 0 : *aFitWidth = width;
7446 0 : return last;
7447 : }
7448 :
7449 : nsIFrame::ContentOffsets
7450 0 : nsTextFrame::CalcContentOffsetsFromFramePoint(const nsPoint& aPoint)
7451 : {
7452 0 : return GetCharacterOffsetAtFramePointInternal(aPoint, true);
7453 : }
7454 :
7455 : nsIFrame::ContentOffsets
7456 0 : nsTextFrame::GetCharacterOffsetAtFramePoint(const nsPoint &aPoint)
7457 : {
7458 0 : return GetCharacterOffsetAtFramePointInternal(aPoint, false);
7459 : }
7460 :
7461 : nsIFrame::ContentOffsets
7462 0 : nsTextFrame::GetCharacterOffsetAtFramePointInternal(const nsPoint& aPoint,
7463 : bool aForInsertionPoint)
7464 : {
7465 0 : ContentOffsets offsets;
7466 :
7467 0 : gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
7468 0 : if (!mTextRun)
7469 : return offsets;
7470 :
7471 0 : PropertyProvider provider(this, iter, nsTextFrame::eInflated);
7472 : // Trim leading but not trailing whitespace if possible
7473 0 : provider.InitializeForDisplay(false);
7474 0 : gfxFloat width = mTextRun->IsVertical()
7475 0 : ? (mTextRun->IsInlineReversed() ? mRect.height - aPoint.y : aPoint.y)
7476 0 : : (mTextRun->IsInlineReversed() ? mRect.width - aPoint.x : aPoint.x);
7477 0 : if (Style()->IsTextCombined()) {
7478 0 : width /= GetTextCombineScaleFactor(this);
7479 : }
7480 : gfxFloat fitWidth;
7481 0 : Range skippedRange = ComputeTransformedRange(provider);
7482 :
7483 : uint32_t charsFit = CountCharsFit(mTextRun, skippedRange,
7484 0 : width, &provider, &fitWidth);
7485 :
7486 : int32_t selectedOffset;
7487 0 : if (charsFit < skippedRange.Length()) {
7488 : // charsFit characters fitted, but no more could fit. See if we're
7489 : // more than halfway through the cluster.. If we are, choose the next
7490 : // cluster.
7491 0 : gfxSkipCharsIterator extraCluster(provider.GetStart());
7492 0 : extraCluster.AdvanceSkipped(charsFit);
7493 :
7494 0 : bool allowSplitLigature = true; // Allow selection of partial ligature...
7495 :
7496 : // ...but don't let selection/insertion-point split two Regional Indicator
7497 : // chars that are ligated in the textrun to form a single flag symbol.
7498 0 : uint32_t offs = extraCluster.GetOriginalOffset();
7499 0 : const nsTextFragment* frag = GetContent()->GetText();
7500 0 : if (offs + 1 < frag->GetLength() &&
7501 0 : NS_IS_HIGH_SURROGATE(frag->CharAt(offs)) &&
7502 0 : NS_IS_LOW_SURROGATE(frag->CharAt(offs + 1)) &&
7503 : gfxFontUtils::IsRegionalIndicator
7504 0 : (SURROGATE_TO_UCS4(frag->CharAt(offs), frag->CharAt(offs + 1)))) {
7505 0 : allowSplitLigature = false;
7506 0 : if (extraCluster.GetSkippedOffset() > 1 &&
7507 0 : !mTextRun->IsLigatureGroupStart(extraCluster.GetSkippedOffset())) {
7508 : // CountCharsFit() left us in the middle of the flag; back up over the
7509 : // first character of the ligature, and adjust fitWidth accordingly.
7510 0 : extraCluster.AdvanceSkipped(-2); // it's a surrogate pair: 2 code units
7511 0 : fitWidth -= mTextRun->GetAdvanceWidth(
7512 : Range(extraCluster.GetSkippedOffset(),
7513 0 : extraCluster.GetSkippedOffset() + 2), &provider);
7514 : }
7515 : }
7516 :
7517 0 : gfxSkipCharsIterator extraClusterLastChar(extraCluster);
7518 0 : FindClusterEnd(mTextRun,
7519 0 : provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength(),
7520 0 : &extraClusterLastChar, allowSplitLigature);
7521 : PropertyProvider::Spacing spacing;
7522 : Range extraClusterRange(extraCluster.GetSkippedOffset(),
7523 0 : extraClusterLastChar.GetSkippedOffset() + 1);
7524 : gfxFloat charWidth =
7525 0 : mTextRun->GetAdvanceWidth(extraClusterRange, &provider, &spacing);
7526 0 : charWidth -= spacing.mBefore + spacing.mAfter;
7527 0 : selectedOffset = !aForInsertionPoint ||
7528 0 : width <= fitWidth + spacing.mBefore + charWidth/2
7529 0 : ? extraCluster.GetOriginalOffset()
7530 0 : : extraClusterLastChar.GetOriginalOffset() + 1;
7531 : } else {
7532 : // All characters fitted, we're at (or beyond) the end of the text.
7533 : // XXX This could be some pathological situation where negative spacing
7534 : // caused characters to move backwards. We can't really handle that
7535 : // in the current frame system because frames can't have negative
7536 : // intrinsic widths.
7537 0 : selectedOffset =
7538 0 : provider.GetStart().GetOriginalOffset() + provider.GetOriginalLength();
7539 : // If we're at the end of a preformatted line which has a terminating
7540 : // linefeed, we want to reduce the offset by one to make sure that the
7541 : // selection is placed before the linefeed character.
7542 0 : if (HasSignificantTerminalNewline()) {
7543 0 : --selectedOffset;
7544 : }
7545 : }
7546 :
7547 0 : offsets.content = GetContent();
7548 0 : offsets.offset = offsets.secondaryOffset = selectedOffset;
7549 0 : offsets.associate =
7550 0 : mContentOffset == offsets.offset ? CARET_ASSOCIATE_AFTER : CARET_ASSOCIATE_BEFORE;
7551 : return offsets;
7552 : }
7553 :
7554 : bool
7555 0 : nsTextFrame::CombineSelectionUnderlineRect(nsPresContext* aPresContext,
7556 : nsRect& aRect)
7557 : {
7558 0 : if (aRect.IsEmpty())
7559 : return false;
7560 :
7561 0 : nsRect givenRect = aRect;
7562 :
7563 : RefPtr<nsFontMetrics> fm =
7564 0 : nsLayoutUtils::GetFontMetricsForFrame(this, GetFontSizeInflation());
7565 0 : gfxFontGroup* fontGroup = fm->GetThebesFontGroup();
7566 0 : gfxFont* firstFont = fontGroup->GetFirstValidFont();
7567 0 : WritingMode wm = GetWritingMode();
7568 0 : bool verticalRun = wm.IsVertical();
7569 0 : bool useVerticalMetrics = verticalRun && !wm.IsSideways();
7570 : const gfxFont::Metrics& metrics =
7571 : firstFont->GetMetrics(useVerticalMetrics ? gfxFont::eVertical
7572 0 : : gfxFont::eHorizontal);
7573 :
7574 0 : nsCSSRendering::DecorationRectParams params;
7575 0 : params.ascent = aPresContext->AppUnitsToGfxUnits(mAscent);
7576 0 : params.offset = fontGroup->GetUnderlineOffset();
7577 0 : params.descentLimit =
7578 0 : ComputeDescentLimitForSelectionUnderline(aPresContext, metrics);
7579 0 : params.vertical = verticalRun;
7580 :
7581 0 : EnsureTextRun(nsTextFrame::eInflated);
7582 0 : params.sidewaysLeft = mTextRun ? mTextRun->IsSidewaysLeft() : false;
7583 :
7584 0 : UniquePtr<SelectionDetails> details = GetSelectionDetails();
7585 0 : for (SelectionDetails* sd = details.get(); sd; sd = sd->mNext.get()) {
7586 0 : if (sd->mStart == sd->mEnd ||
7587 0 : sd->mSelectionType == SelectionType::eInvalid ||
7588 0 : !(ToSelectionTypeMask(sd->mSelectionType) &
7589 0 : kSelectionTypesWithDecorations) ||
7590 : // URL strikeout does not use underline.
7591 0 : sd->mSelectionType == SelectionType::eURLStrikeout) {
7592 0 : continue;
7593 : }
7594 :
7595 : float relativeSize;
7596 : int32_t index =
7597 0 : nsTextPaintStyle::GetUnderlineStyleIndexForSelectionType(
7598 0 : sd->mSelectionType);
7599 0 : if (sd->mSelectionType == SelectionType::eSpellCheck) {
7600 0 : if (!nsTextPaintStyle::GetSelectionUnderline(aPresContext, index, nullptr,
7601 : &relativeSize,
7602 : ¶ms.style)) {
7603 : continue;
7604 : }
7605 : } else {
7606 : // IME selections
7607 0 : TextRangeStyle& rangeStyle = sd->mTextRangeStyle;
7608 0 : if (rangeStyle.IsDefined()) {
7609 0 : if (!rangeStyle.IsLineStyleDefined() ||
7610 0 : rangeStyle.mLineStyle == TextRangeStyle::LINESTYLE_NONE) {
7611 : continue;
7612 : }
7613 0 : params.style = rangeStyle.mLineStyle;
7614 0 : relativeSize = rangeStyle.mIsBoldLine ? 2.0f : 1.0f;
7615 0 : } else if (!nsTextPaintStyle::GetSelectionUnderline(aPresContext, index,
7616 : nullptr, &relativeSize,
7617 : ¶ms.style)) {
7618 : continue;
7619 : }
7620 : }
7621 0 : nsRect decorationArea;
7622 :
7623 0 : params.lineSize =
7624 0 : Size(aPresContext->AppUnitsToGfxUnits(aRect.width),
7625 0 : ComputeSelectionUnderlineHeight(aPresContext, metrics,
7626 : sd->mSelectionType));
7627 0 : relativeSize = std::max(relativeSize, 1.0f);
7628 0 : params.lineSize.height *= relativeSize;
7629 0 : decorationArea =
7630 0 : nsCSSRendering::GetTextDecorationRect(aPresContext, params);
7631 0 : aRect.UnionRect(aRect, decorationArea);
7632 : }
7633 :
7634 0 : return !aRect.IsEmpty() && !givenRect.Contains(aRect);
7635 : }
7636 :
7637 : bool
7638 0 : nsTextFrame::IsFrameSelected() const
7639 : {
7640 0 : NS_ASSERTION(!GetContent() || GetContent()->IsSelectionDescendant(),
7641 : "use the public IsSelected() instead");
7642 0 : return nsRange::IsNodeSelected(GetContent(), GetContentOffset(),
7643 0 : GetContentEnd());
7644 : }
7645 :
7646 : void
7647 0 : nsTextFrame::SetSelectedRange(uint32_t aStart, uint32_t aEnd, bool aSelected,
7648 : SelectionType aSelectionType)
7649 : {
7650 0 : NS_ASSERTION(!GetPrevContinuation(), "Should only be called for primary frame");
7651 : DEBUG_VERIFY_NOT_DIRTY(mState);
7652 :
7653 : // Selection is collapsed, which can't affect text frame rendering
7654 0 : if (aStart == aEnd)
7655 : return;
7656 :
7657 : nsTextFrame* f = this;
7658 0 : while (f && f->GetContentEnd() <= int32_t(aStart)) {
7659 0 : f = f->GetNextContinuation();
7660 : }
7661 :
7662 0 : nsPresContext* presContext = PresContext();
7663 0 : while (f && f->GetContentOffset() < int32_t(aEnd)) {
7664 : // We may need to reflow to recompute the overflow area for
7665 : // spellchecking or IME underline if their underline is thicker than
7666 : // the normal decoration line.
7667 0 : if (ToSelectionTypeMask(aSelectionType) & kSelectionTypesWithDecorations) {
7668 : bool didHaveOverflowingSelection =
7669 0 : (f->GetStateBits() & TEXT_SELECTION_UNDERLINE_OVERFLOWED) != 0;
7670 0 : nsRect r(nsPoint(0, 0), GetSize());
7671 0 : if (didHaveOverflowingSelection ||
7672 0 : (aSelected && f->CombineSelectionUnderlineRect(presContext, r))) {
7673 0 : presContext->PresShell()->FrameNeedsReflow(f,
7674 : nsIPresShell::eStyleChange,
7675 0 : NS_FRAME_IS_DIRTY);
7676 : }
7677 : }
7678 : // Selection might change anything. Invalidate the overflow area.
7679 0 : f->InvalidateFrame();
7680 :
7681 0 : f = f->GetNextContinuation();
7682 : }
7683 : }
7684 :
7685 : void
7686 0 : nsTextFrame::UpdateIteratorFromOffset(const PropertyProvider& aProperties,
7687 : int32_t& aInOffset,
7688 : gfxSkipCharsIterator& aIter)
7689 : {
7690 0 : if (aInOffset < GetContentOffset()){
7691 0 : NS_WARNING("offset before this frame's content");
7692 0 : aInOffset = GetContentOffset();
7693 0 : } else if (aInOffset > GetContentEnd()) {
7694 0 : NS_WARNING("offset after this frame's content");
7695 0 : aInOffset = GetContentEnd();
7696 : }
7697 :
7698 0 : int32_t trimmedOffset = aProperties.GetStart().GetOriginalOffset();
7699 0 : int32_t trimmedEnd = trimmedOffset + aProperties.GetOriginalLength();
7700 0 : aInOffset = std::max(aInOffset, trimmedOffset);
7701 0 : aInOffset = std::min(aInOffset, trimmedEnd);
7702 :
7703 0 : aIter.SetOriginalOffset(aInOffset);
7704 :
7705 0 : if (aInOffset < trimmedEnd &&
7706 0 : !aIter.IsOriginalCharSkipped() &&
7707 0 : !mTextRun->IsClusterStart(aIter.GetSkippedOffset())) {
7708 0 : NS_WARNING("called for non-cluster boundary");
7709 0 : FindClusterStart(mTextRun, trimmedOffset, &aIter);
7710 : }
7711 0 : }
7712 :
7713 : nsPoint
7714 0 : nsTextFrame::GetPointFromIterator(const gfxSkipCharsIterator& aIter,
7715 : PropertyProvider& aProperties)
7716 : {
7717 0 : Range range(aProperties.GetStart().GetSkippedOffset(),
7718 0 : aIter.GetSkippedOffset());
7719 0 : gfxFloat advance = mTextRun->GetAdvanceWidth(range, &aProperties);
7720 0 : nscoord iSize = NSToCoordCeilClamped(advance);
7721 0 : nsPoint point;
7722 :
7723 0 : if (mTextRun->IsVertical()) {
7724 0 : point.x = 0;
7725 0 : if (mTextRun->IsInlineReversed()) {
7726 0 : point.y = mRect.height - iSize;
7727 : } else {
7728 0 : point.y = iSize;
7729 : }
7730 : } else {
7731 0 : point.y = 0;
7732 0 : if (mTextRun->IsInlineReversed()) {
7733 0 : point.x = mRect.width - iSize;
7734 : } else {
7735 0 : point.x = iSize;
7736 : }
7737 0 : if (Style()->IsTextCombined()) {
7738 0 : point.x *= GetTextCombineScaleFactor(this);
7739 : }
7740 : }
7741 0 : return point;
7742 : }
7743 :
7744 : nsresult
7745 0 : nsTextFrame::GetPointFromOffset(int32_t inOffset,
7746 : nsPoint* outPoint)
7747 : {
7748 0 : if (!outPoint)
7749 : return NS_ERROR_NULL_POINTER;
7750 :
7751 : DEBUG_VERIFY_NOT_DIRTY(mState);
7752 0 : if (mState & NS_FRAME_IS_DIRTY)
7753 : return NS_ERROR_UNEXPECTED;
7754 :
7755 0 : if (GetContentLength() <= 0) {
7756 0 : outPoint->x = 0;
7757 0 : outPoint->y = 0;
7758 0 : return NS_OK;
7759 : }
7760 :
7761 0 : gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
7762 0 : if (!mTextRun)
7763 : return NS_ERROR_FAILURE;
7764 :
7765 0 : PropertyProvider properties(this, iter, nsTextFrame::eInflated);
7766 : // Don't trim trailing whitespace, we want the caret to appear in the right
7767 : // place if it's positioned there
7768 0 : properties.InitializeForDisplay(false);
7769 :
7770 0 : UpdateIteratorFromOffset(properties, inOffset, iter);
7771 :
7772 0 : *outPoint = GetPointFromIterator(iter, properties);
7773 :
7774 : return NS_OK;
7775 : }
7776 :
7777 : nsresult
7778 0 : nsTextFrame::GetCharacterRectsInRange(int32_t aInOffset,
7779 : int32_t aLength,
7780 : nsTArray<nsRect>& aRects)
7781 : {
7782 : DEBUG_VERIFY_NOT_DIRTY(mState);
7783 0 : if (mState & NS_FRAME_IS_DIRTY) {
7784 : return NS_ERROR_UNEXPECTED;
7785 : }
7786 :
7787 0 : if (GetContentLength() <= 0) {
7788 : return NS_OK;
7789 : }
7790 :
7791 0 : if (!mTextRun) {
7792 : return NS_ERROR_FAILURE;
7793 : }
7794 :
7795 0 : gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
7796 0 : PropertyProvider properties(this, iter, nsTextFrame::eInflated);
7797 : // Don't trim trailing whitespace, we want the caret to appear in the right
7798 : // place if it's positioned there
7799 0 : properties.InitializeForDisplay(false);
7800 :
7801 0 : UpdateIteratorFromOffset(properties, aInOffset, iter);
7802 :
7803 0 : const int32_t kContentEnd = GetContentEnd();
7804 0 : const int32_t kEndOffset = std::min(aInOffset + aLength, kContentEnd);
7805 0 : while (aInOffset < kEndOffset) {
7806 0 : if (!iter.IsOriginalCharSkipped() &&
7807 0 : !mTextRun->IsClusterStart(iter.GetSkippedOffset())) {
7808 0 : FindClusterStart(mTextRun,
7809 0 : properties.GetStart().GetOriginalOffset() +
7810 0 : properties.GetOriginalLength(),
7811 0 : &iter);
7812 : }
7813 :
7814 0 : nsPoint point = GetPointFromIterator(iter, properties);
7815 0 : nsRect rect;
7816 0 : rect.x = point.x;
7817 0 : rect.y = point.y;
7818 :
7819 0 : nscoord iSize = 0;
7820 0 : if (aInOffset < kContentEnd) {
7821 0 : gfxSkipCharsIterator nextIter(iter);
7822 0 : nextIter.AdvanceOriginal(1);
7823 0 : if (!nextIter.IsOriginalCharSkipped() &&
7824 0 : !mTextRun->IsClusterStart(nextIter.GetSkippedOffset())) {
7825 0 : FindClusterEnd(mTextRun, kContentEnd, &nextIter);
7826 : }
7827 :
7828 : gfxFloat advance =
7829 0 : mTextRun->GetAdvanceWidth(Range(iter.GetSkippedOffset(),
7830 : nextIter.GetSkippedOffset()),
7831 0 : &properties);
7832 0 : iSize = NSToCoordCeilClamped(advance);
7833 : }
7834 :
7835 0 : if (mTextRun->IsVertical()) {
7836 0 : rect.width = mRect.width;
7837 0 : rect.height = iSize;
7838 : } else {
7839 0 : rect.width = iSize;
7840 0 : rect.height = mRect.height;
7841 :
7842 0 : if (Style()->IsTextCombined()) {
7843 0 : rect.width *= GetTextCombineScaleFactor(this);
7844 : }
7845 : }
7846 0 : aRects.AppendElement(rect);
7847 0 : aInOffset++;
7848 : // Don't advance iter if we've reached the end
7849 0 : if (aInOffset < kEndOffset) {
7850 0 : iter.AdvanceOriginal(1);
7851 : }
7852 : }
7853 :
7854 : return NS_OK;
7855 : }
7856 :
7857 : nsresult
7858 0 : nsTextFrame::GetChildFrameContainingOffset(int32_t aContentOffset,
7859 : bool aHint,
7860 : int32_t* aOutOffset,
7861 : nsIFrame**aOutFrame)
7862 : {
7863 : DEBUG_VERIFY_NOT_DIRTY(mState);
7864 : #if 0 //XXXrbs disable due to bug 310227
7865 : if (mState & NS_FRAME_IS_DIRTY)
7866 : return NS_ERROR_UNEXPECTED;
7867 : #endif
7868 :
7869 0 : NS_ASSERTION(aOutOffset && aOutFrame, "Bad out parameters");
7870 0 : NS_ASSERTION(aContentOffset >= 0, "Negative content offset, existing code was very broken!");
7871 0 : nsIFrame* primaryFrame = mContent->GetPrimaryFrame();
7872 0 : if (this != primaryFrame) {
7873 : // This call needs to happen on the primary frame
7874 0 : return primaryFrame->GetChildFrameContainingOffset(aContentOffset, aHint,
7875 0 : aOutOffset, aOutFrame);
7876 : }
7877 :
7878 0 : nsTextFrame* f = this;
7879 0 : int32_t offset = mContentOffset;
7880 :
7881 : // Try to look up the offset to frame property
7882 0 : nsTextFrame* cachedFrame = GetProperty(OffsetToFrameProperty());
7883 :
7884 0 : if (cachedFrame) {
7885 0 : f = cachedFrame;
7886 0 : offset = f->GetContentOffset();
7887 :
7888 0 : f->RemoveStateBits(TEXT_IN_OFFSET_CACHE);
7889 : }
7890 :
7891 0 : if ((aContentOffset >= offset) &&
7892 0 : (aHint || aContentOffset != offset)) {
7893 : while (true) {
7894 0 : nsTextFrame* next = f->GetNextContinuation();
7895 0 : if (!next || aContentOffset < next->GetContentOffset())
7896 : break;
7897 0 : if (aContentOffset == next->GetContentOffset()) {
7898 0 : if (aHint) {
7899 0 : f = next;
7900 0 : if (f->GetContentLength() == 0) {
7901 : continue; // use the last of the empty frames with this offset
7902 : }
7903 : }
7904 : break;
7905 : }
7906 : f = next;
7907 : }
7908 : } else {
7909 : while (true) {
7910 0 : nsTextFrame* prev = f->GetPrevContinuation();
7911 0 : if (!prev || aContentOffset > f->GetContentOffset())
7912 : break;
7913 0 : if (aContentOffset == f->GetContentOffset()) {
7914 0 : if (!aHint) {
7915 0 : f = prev;
7916 0 : if (f->GetContentLength() == 0) {
7917 : continue; // use the first of the empty frames with this offset
7918 : }
7919 : }
7920 : break;
7921 : }
7922 : f = prev;
7923 : }
7924 : }
7925 :
7926 0 : *aOutOffset = aContentOffset - f->GetContentOffset();
7927 0 : *aOutFrame = f;
7928 :
7929 : // cache the frame we found
7930 0 : SetProperty(OffsetToFrameProperty(), f);
7931 0 : f->AddStateBits(TEXT_IN_OFFSET_CACHE);
7932 :
7933 0 : return NS_OK;
7934 : }
7935 :
7936 : nsIFrame::FrameSearchResult
7937 0 : nsTextFrame::PeekOffsetNoAmount(bool aForward, int32_t* aOffset)
7938 : {
7939 0 : NS_ASSERTION(aOffset && *aOffset <= GetContentLength(), "aOffset out of range");
7940 :
7941 0 : gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
7942 0 : if (!mTextRun)
7943 : return CONTINUE_EMPTY;
7944 :
7945 0 : TrimmedOffsets trimmed = GetTrimmedOffsets(mContent->GetText(), true);
7946 : // Check whether there are nonskipped characters in the trimmmed range
7947 0 : return (iter.ConvertOriginalToSkipped(trimmed.GetEnd()) >
7948 0 : iter.ConvertOriginalToSkipped(trimmed.mStart)) ? FOUND : CONTINUE;
7949 : }
7950 :
7951 : /**
7952 : * This class iterates through the clusters before or after the given
7953 : * aPosition (which is a content offset). You can test each cluster
7954 : * to see if it's whitespace (as far as selection/caret movement is concerned),
7955 : * or punctuation, or if there is a word break before the cluster. ("Before"
7956 : * is interpreted according to aDirection, so if aDirection is -1, "before"
7957 : * means actually *after* the cluster content.)
7958 : */
7959 0 : class MOZ_STACK_CLASS ClusterIterator {
7960 : public:
7961 : ClusterIterator(nsTextFrame* aTextFrame, int32_t aPosition, int32_t aDirection,
7962 : nsString& aContext);
7963 :
7964 : bool NextCluster();
7965 : bool IsWhitespace();
7966 : bool IsPunctuation();
7967 : bool HaveWordBreakBefore() { return mHaveWordBreak; }
7968 :
7969 : // Get the charIndex that corresponds to the "before" side of the current
7970 : // character, according to the direction of iteration: so for a forward
7971 : // iterator, this is simply mCharIndex, while for a reverse iterator it will
7972 : // be mCharIndex + <number of code units in the character>.
7973 0 : int32_t GetBeforeOffset()
7974 : {
7975 0 : MOZ_ASSERT(mCharIndex >= 0);
7976 0 : return mDirection < 0 ? GetAfterInternal() : mCharIndex;
7977 : }
7978 : // Get the charIndex that corresponds to the "before" side of the current
7979 : // character, according to the direction of iteration: the opposite side
7980 : // to what GetBeforeOffset returns.
7981 0 : int32_t GetAfterOffset()
7982 : {
7983 0 : MOZ_ASSERT(mCharIndex >= 0);
7984 0 : return mDirection > 0 ? GetAfterInternal() : mCharIndex;
7985 : }
7986 :
7987 : private:
7988 : // Helper for Get{After,Before}Offset; returns the charIndex after the
7989 : // current position in the text, accounting for surrogate pairs.
7990 : int32_t GetAfterInternal();
7991 :
7992 : gfxSkipCharsIterator mIterator;
7993 : const nsTextFragment* mFrag;
7994 : nsTextFrame* mTextFrame;
7995 : int32_t mDirection; // +1 or -1, or 0 to indicate failure
7996 : int32_t mCharIndex;
7997 : nsTextFrame::TrimmedOffsets mTrimmed;
7998 : nsTArray<bool> mWordBreaks;
7999 : bool mHaveWordBreak;
8000 : };
8001 :
8002 : static bool
8003 0 : IsAcceptableCaretPosition(const gfxSkipCharsIterator& aIter,
8004 : bool aRespectClusters,
8005 : const gfxTextRun* aTextRun,
8006 : nsIFrame* aFrame)
8007 : {
8008 0 : if (aIter.IsOriginalCharSkipped())
8009 : return false;
8010 0 : uint32_t index = aIter.GetSkippedOffset();
8011 0 : if (aRespectClusters && !aTextRun->IsClusterStart(index))
8012 : return false;
8013 0 : if (index > 0) {
8014 : // Check whether the proposed position is in between the two halves of a
8015 : // surrogate pair, or before a Variation Selector character;
8016 : // if so, this is not a valid character boundary.
8017 : // (In the case where we are respecting clusters, we won't actually get
8018 : // this far because the low surrogate is also marked as non-clusterStart
8019 : // so we'll return FALSE above.)
8020 0 : uint32_t offs = aIter.GetOriginalOffset();
8021 0 : const nsTextFragment* frag = aFrame->GetContent()->GetText();
8022 0 : uint32_t ch = frag->CharAt(offs);
8023 :
8024 0 : if (gfxFontUtils::IsVarSelector(ch) ||
8025 0 : (NS_IS_LOW_SURROGATE(ch) && offs > 0 &&
8026 0 : NS_IS_HIGH_SURROGATE(frag->CharAt(offs - 1)))) {
8027 : return false;
8028 : }
8029 :
8030 : // If the proposed position is before a high surrogate, we need to decode
8031 : // the surrogate pair (if valid) and check the resulting character.
8032 0 : if (NS_IS_HIGH_SURROGATE(ch) && offs + 1 < frag->GetLength()) {
8033 0 : uint32_t ch2 = frag->CharAt(offs + 1);
8034 0 : if (NS_IS_LOW_SURROGATE(ch2)) {
8035 0 : ch = SURROGATE_TO_UCS4(ch, ch2);
8036 : // If the character is a (Plane-14) variation selector,
8037 : // or a Regional Indicator character that is ligated with the previous
8038 : // character, this is not a valid boundary.
8039 0 : if (gfxFontUtils::IsVarSelector(ch) ||
8040 0 : (gfxFontUtils::IsRegionalIndicator(ch) &&
8041 0 : !aTextRun->IsLigatureGroupStart(index))) {
8042 : return false;
8043 : }
8044 : }
8045 : }
8046 : }
8047 : return true;
8048 : }
8049 :
8050 : nsIFrame::FrameSearchResult
8051 0 : nsTextFrame::PeekOffsetCharacter(bool aForward, int32_t* aOffset,
8052 : PeekOffsetCharacterOptions aOptions)
8053 : {
8054 0 : int32_t contentLength = GetContentLength();
8055 0 : NS_ASSERTION(aOffset && *aOffset <= contentLength, "aOffset out of range");
8056 :
8057 0 : if (!aOptions.mIgnoreUserStyleAll) {
8058 : StyleUserSelect selectStyle;
8059 0 : IsSelectable(&selectStyle);
8060 0 : if (selectStyle == StyleUserSelect::All) {
8061 0 : return CONTINUE_UNSELECTABLE;
8062 : }
8063 : }
8064 :
8065 0 : gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
8066 0 : if (!mTextRun)
8067 : return CONTINUE_EMPTY;
8068 :
8069 0 : TrimmedOffsets trimmed = GetTrimmedOffsets(mContent->GetText(), false);
8070 :
8071 : // A negative offset means "end of frame".
8072 0 : int32_t startOffset = GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset);
8073 :
8074 0 : if (!aForward) {
8075 : // If at the beginning of the line, look at the previous continuation
8076 0 : for (int32_t i = std::min(trimmed.GetEnd(), startOffset) - 1;
8077 0 : i >= trimmed.mStart; --i) {
8078 0 : iter.SetOriginalOffset(i);
8079 0 : if (IsAcceptableCaretPosition(iter, aOptions.mRespectClusters, mTextRun,
8080 0 : this)) {
8081 0 : *aOffset = i - mContentOffset;
8082 0 : return FOUND;
8083 : }
8084 : }
8085 0 : *aOffset = 0;
8086 : } else {
8087 : // If we're at the end of a line, look at the next continuation
8088 0 : iter.SetOriginalOffset(startOffset);
8089 0 : if (startOffset <= trimmed.GetEnd() &&
8090 0 : !(startOffset < trimmed.GetEnd() &&
8091 0 : StyleText()->NewlineIsSignificant(this) &&
8092 0 : iter.GetSkippedOffset() < mTextRun->GetLength() &&
8093 0 : mTextRun->CharIsNewline(iter.GetSkippedOffset()))) {
8094 0 : for (int32_t i = startOffset + 1; i <= trimmed.GetEnd(); ++i) {
8095 0 : iter.SetOriginalOffset(i);
8096 0 : if (i == trimmed.GetEnd() ||
8097 0 : IsAcceptableCaretPosition(iter, aOptions.mRespectClusters, mTextRun,
8098 0 : this)) {
8099 0 : *aOffset = i - mContentOffset;
8100 0 : return FOUND;
8101 : }
8102 : }
8103 : }
8104 0 : *aOffset = contentLength;
8105 : }
8106 :
8107 : return CONTINUE;
8108 : }
8109 :
8110 : bool
8111 0 : ClusterIterator::IsWhitespace()
8112 : {
8113 0 : NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
8114 0 : return IsSelectionSpace(mFrag, mCharIndex);
8115 : }
8116 :
8117 : bool
8118 0 : ClusterIterator::IsPunctuation()
8119 : {
8120 0 : NS_ASSERTION(mCharIndex >= 0, "No cluster selected");
8121 : // The pref is cached on first call; changes will require a browser restart.
8122 : static bool sStopAtUnderscore =
8123 0 : Preferences::GetBool("layout.word_select.stop_at_underscore", false);
8124 : // Return true for all Punctuation categories (Unicode general category P?),
8125 : // and also for Symbol categories (S?) except for Modifier Symbol, which is
8126 : // kept together with any adjacent letter/number. (Bug 1066756)
8127 0 : uint32_t ch = mFrag->CharAt(mCharIndex);
8128 0 : uint8_t cat = unicode::GetGeneralCategory(ch);
8129 : switch (cat) {
8130 : case HB_UNICODE_GENERAL_CATEGORY_CONNECT_PUNCTUATION: /* Pc */
8131 0 : if (ch == '_' && !sStopAtUnderscore) {
8132 : return false;
8133 : }
8134 : MOZ_FALLTHROUGH;
8135 : case HB_UNICODE_GENERAL_CATEGORY_DASH_PUNCTUATION: /* Pd */
8136 : case HB_UNICODE_GENERAL_CATEGORY_CLOSE_PUNCTUATION: /* Pe */
8137 : case HB_UNICODE_GENERAL_CATEGORY_FINAL_PUNCTUATION: /* Pf */
8138 : case HB_UNICODE_GENERAL_CATEGORY_INITIAL_PUNCTUATION: /* Pi */
8139 : case HB_UNICODE_GENERAL_CATEGORY_OTHER_PUNCTUATION: /* Po */
8140 : case HB_UNICODE_GENERAL_CATEGORY_OPEN_PUNCTUATION: /* Ps */
8141 : case HB_UNICODE_GENERAL_CATEGORY_CURRENCY_SYMBOL: /* Sc */
8142 : // Deliberately omitted:
8143 : // case HB_UNICODE_GENERAL_CATEGORY_MODIFIER_SYMBOL: /* Sk */
8144 : case HB_UNICODE_GENERAL_CATEGORY_MATH_SYMBOL: /* Sm */
8145 : case HB_UNICODE_GENERAL_CATEGORY_OTHER_SYMBOL: /* So */
8146 0 : return true;
8147 : default:
8148 : return false;
8149 : }
8150 : }
8151 :
8152 : int32_t
8153 0 : ClusterIterator::GetAfterInternal()
8154 : {
8155 0 : if (mFrag->Is2b() &&
8156 0 : NS_IS_HIGH_SURROGATE(mFrag->Get2b()[mCharIndex]) &&
8157 0 : uint32_t(mCharIndex) + 1 < mFrag->GetLength() &&
8158 0 : NS_IS_LOW_SURROGATE(mFrag->Get2b()[mCharIndex + 1])) {
8159 0 : return mCharIndex + 2;
8160 : }
8161 0 : return mCharIndex + 1;
8162 : }
8163 :
8164 : bool
8165 0 : ClusterIterator::NextCluster()
8166 : {
8167 0 : if (!mDirection)
8168 : return false;
8169 0 : const gfxTextRun* textRun = mTextFrame->GetTextRun(nsTextFrame::eInflated);
8170 :
8171 0 : mHaveWordBreak = false;
8172 : while (true) {
8173 0 : bool keepGoing = false;
8174 0 : if (mDirection > 0) {
8175 0 : if (mIterator.GetOriginalOffset() >= mTrimmed.GetEnd())
8176 : return false;
8177 0 : keepGoing = mIterator.IsOriginalCharSkipped() ||
8178 0 : mIterator.GetOriginalOffset() < mTrimmed.mStart ||
8179 0 : !textRun->IsClusterStart(mIterator.GetSkippedOffset());
8180 0 : mCharIndex = mIterator.GetOriginalOffset();
8181 0 : mIterator.AdvanceOriginal(1);
8182 : } else {
8183 0 : if (mIterator.GetOriginalOffset() <= mTrimmed.mStart)
8184 : return false;
8185 0 : mIterator.AdvanceOriginal(-1);
8186 0 : keepGoing = mIterator.IsOriginalCharSkipped() ||
8187 0 : mIterator.GetOriginalOffset() >= mTrimmed.GetEnd() ||
8188 0 : !textRun->IsClusterStart(mIterator.GetSkippedOffset());
8189 0 : mCharIndex = mIterator.GetOriginalOffset();
8190 : }
8191 :
8192 0 : if (mWordBreaks[GetBeforeOffset() - mTextFrame->GetContentOffset()]) {
8193 0 : mHaveWordBreak = true;
8194 : }
8195 0 : if (!keepGoing)
8196 : return true;
8197 : }
8198 : }
8199 :
8200 0 : ClusterIterator::ClusterIterator(nsTextFrame* aTextFrame, int32_t aPosition,
8201 0 : int32_t aDirection, nsString& aContext)
8202 0 : : mTextFrame(aTextFrame), mDirection(aDirection), mCharIndex(-1)
8203 : {
8204 0 : mIterator = aTextFrame->EnsureTextRun(nsTextFrame::eInflated);
8205 0 : if (!aTextFrame->GetTextRun(nsTextFrame::eInflated)) {
8206 0 : mDirection = 0; // signal failure
8207 0 : return;
8208 : }
8209 0 : mIterator.SetOriginalOffset(aPosition);
8210 :
8211 0 : mFrag = aTextFrame->GetContent()->GetText();
8212 0 : mTrimmed = aTextFrame->GetTrimmedOffsets(mFrag, true);
8213 :
8214 0 : int32_t textOffset = aTextFrame->GetContentOffset();
8215 0 : int32_t textLen = aTextFrame->GetContentLength();
8216 0 : if (!mWordBreaks.AppendElements(textLen + 1)) {
8217 0 : mDirection = 0; // signal failure
8218 0 : return;
8219 : }
8220 0 : memset(mWordBreaks.Elements(), false, (textLen + 1)*sizeof(bool));
8221 : int32_t textStart;
8222 0 : if (aDirection > 0) {
8223 0 : if (aContext.IsEmpty()) {
8224 : // No previous context, so it must be the start of a line or text run
8225 0 : mWordBreaks[0] = true;
8226 : }
8227 0 : textStart = aContext.Length();
8228 0 : mFrag->AppendTo(aContext, textOffset, textLen);
8229 : } else {
8230 0 : if (aContext.IsEmpty()) {
8231 : // No following context, so it must be the end of a line or text run
8232 0 : mWordBreaks[textLen] = true;
8233 : }
8234 0 : textStart = 0;
8235 0 : nsAutoString str;
8236 0 : mFrag->AppendTo(str, textOffset, textLen);
8237 0 : aContext.Insert(str, 0);
8238 : }
8239 0 : mozilla::intl::WordBreaker* wordBreaker = nsContentUtils::WordBreaker();
8240 0 : for (int32_t i = 0; i <= textLen; ++i) {
8241 0 : int32_t indexInText = i + textStart;
8242 0 : mWordBreaks[i] |=
8243 0 : wordBreaker->BreakInBetween(aContext.get(), indexInText,
8244 0 : aContext.get() + indexInText,
8245 0 : aContext.Length() - indexInText);
8246 : }
8247 : }
8248 :
8249 : nsIFrame::FrameSearchResult
8250 0 : nsTextFrame::PeekOffsetWord(bool aForward, bool aWordSelectEatSpace, bool aIsKeyboardSelect,
8251 : int32_t* aOffset, PeekWordState* aState)
8252 : {
8253 0 : int32_t contentLength = GetContentLength();
8254 0 : NS_ASSERTION (aOffset && *aOffset <= contentLength, "aOffset out of range");
8255 :
8256 : StyleUserSelect selectStyle;
8257 0 : IsSelectable(&selectStyle);
8258 0 : if (selectStyle == StyleUserSelect::All)
8259 : return CONTINUE_UNSELECTABLE;
8260 :
8261 0 : int32_t offset = GetContentOffset() + (*aOffset < 0 ? contentLength : *aOffset);
8262 0 : ClusterIterator cIter(this, offset, aForward ? 1 : -1, aState->mContext);
8263 :
8264 0 : if (!cIter.NextCluster())
8265 : return CONTINUE_EMPTY;
8266 :
8267 0 : do {
8268 0 : bool isPunctuation = cIter.IsPunctuation();
8269 0 : bool isWhitespace = cIter.IsWhitespace();
8270 0 : bool isWordBreakBefore = cIter.HaveWordBreakBefore();
8271 0 : if (aWordSelectEatSpace == isWhitespace && !aState->mSawBeforeType) {
8272 0 : aState->SetSawBeforeType();
8273 0 : aState->Update(isPunctuation, isWhitespace);
8274 0 : continue;
8275 : }
8276 : // See if we can break before the current cluster
8277 0 : if (!aState->mAtStart) {
8278 : bool canBreak;
8279 0 : if (isPunctuation != aState->mLastCharWasPunctuation) {
8280 0 : canBreak = BreakWordBetweenPunctuation(aState, aForward,
8281 0 : isPunctuation, isWhitespace, aIsKeyboardSelect);
8282 0 : } else if (!aState->mLastCharWasWhitespace &&
8283 0 : !isWhitespace && !isPunctuation && isWordBreakBefore) {
8284 : // if both the previous and the current character are not white
8285 : // space but this can be word break before, we don't need to eat
8286 : // a white space in this case. This case happens in some languages
8287 : // that their words are not separated by white spaces. E.g.,
8288 : // Japanese and Chinese.
8289 : canBreak = true;
8290 : } else {
8291 0 : canBreak = isWordBreakBefore && aState->mSawBeforeType &&
8292 : (aWordSelectEatSpace != isWhitespace);
8293 : }
8294 0 : if (canBreak) {
8295 0 : *aOffset = cIter.GetBeforeOffset() - mContentOffset;
8296 0 : return FOUND;
8297 : }
8298 : }
8299 0 : aState->Update(isPunctuation, isWhitespace);
8300 : } while (cIter.NextCluster());
8301 :
8302 0 : *aOffset = cIter.GetAfterOffset() - mContentOffset;
8303 0 : return CONTINUE;
8304 : }
8305 :
8306 : // TODO this needs to be deCOMtaminated with the interface fixed in
8307 : // nsIFrame.h, but we won't do that until the old textframe is gone.
8308 : nsresult
8309 0 : nsTextFrame::CheckVisibility(nsPresContext* aContext, int32_t aStartIndex,
8310 : int32_t aEndIndex, bool aRecurse, bool *aFinished, bool *aRetval)
8311 : {
8312 0 : if (!aRetval)
8313 : return NS_ERROR_NULL_POINTER;
8314 :
8315 : // Text in the range is visible if there is at least one character in the range
8316 : // that is not skipped and is mapped by this frame (which is the primary frame)
8317 : // or one of its continuations.
8318 0 : for (nsTextFrame* f = this; f; f = f->GetNextContinuation()) {
8319 0 : int32_t dummyOffset = 0;
8320 0 : if (f->PeekOffsetNoAmount(true, &dummyOffset) == FOUND) {
8321 0 : *aRetval = true;
8322 0 : return NS_OK;
8323 : }
8324 : }
8325 :
8326 0 : *aRetval = false;
8327 0 : return NS_OK;
8328 : }
8329 :
8330 : nsresult
8331 0 : nsTextFrame::GetOffsets(int32_t &start, int32_t &end) const
8332 : {
8333 0 : start = GetContentOffset();
8334 0 : end = GetContentEnd();
8335 0 : return NS_OK;
8336 : }
8337 :
8338 : static int32_t
8339 0 : FindEndOfPunctuationRun(const nsTextFragment* aFrag,
8340 : const gfxTextRun* aTextRun,
8341 : gfxSkipCharsIterator* aIter,
8342 : int32_t aOffset,
8343 : int32_t aStart,
8344 : int32_t aEnd)
8345 : {
8346 : int32_t i;
8347 :
8348 0 : for (i = aStart; i < aEnd - aOffset; ++i) {
8349 0 : if (nsContentUtils::IsFirstLetterPunctuationAt(aFrag, aOffset + i)) {
8350 0 : aIter->SetOriginalOffset(aOffset + i);
8351 0 : FindClusterEnd(aTextRun, aEnd, aIter);
8352 0 : i = aIter->GetOriginalOffset() - aOffset;
8353 : } else {
8354 : break;
8355 : }
8356 : }
8357 0 : return i;
8358 : }
8359 :
8360 : /**
8361 : * Returns true if this text frame completes the first-letter, false
8362 : * if it does not contain a true "letter".
8363 : * If returns true, then it also updates aLength to cover just the first-letter
8364 : * text.
8365 : *
8366 : * XXX :first-letter should be handled during frame construction
8367 : * (and it has a good bit in common with nextBidi)
8368 : *
8369 : * @param aLength an in/out parameter: on entry contains the maximum length to
8370 : * return, on exit returns length of the first-letter fragment (which may
8371 : * include leading and trailing punctuation, for example)
8372 : */
8373 : static bool
8374 0 : FindFirstLetterRange(const nsTextFragment* aFrag,
8375 : const gfxTextRun* aTextRun,
8376 : int32_t aOffset, const gfxSkipCharsIterator& aIter,
8377 : int32_t* aLength)
8378 : {
8379 : int32_t i;
8380 0 : int32_t length = *aLength;
8381 0 : int32_t endOffset = aOffset + length;
8382 0 : gfxSkipCharsIterator iter(aIter);
8383 :
8384 : // skip leading whitespace, then consume clusters that start with punctuation
8385 0 : i = FindEndOfPunctuationRun(aFrag, aTextRun, &iter, aOffset,
8386 0 : GetTrimmableWhitespaceCount(aFrag, aOffset, length, 1),
8387 0 : endOffset);
8388 0 : if (i == length)
8389 : return false;
8390 :
8391 : // If the next character is not a letter or number, there is no first-letter.
8392 : // Return true so that we don't go on looking, but set aLength to 0.
8393 0 : if (!nsContentUtils::IsAlphanumericAt(aFrag, aOffset + i)) {
8394 0 : *aLength = 0;
8395 0 : return true;
8396 : }
8397 :
8398 : // consume another cluster (the actual first letter)
8399 :
8400 : // For complex scripts such as Indic and SEAsian, where first-letter
8401 : // should extend to entire orthographic "syllable" clusters, we don't
8402 : // want to allow this to split a ligature.
8403 : bool allowSplitLigature;
8404 :
8405 : typedef unicode::Script Script;
8406 0 : switch (unicode::GetScriptCode(aFrag->CharAt(aOffset + i))) {
8407 : default:
8408 : allowSplitLigature = true;
8409 : break;
8410 :
8411 : // For now, lacking any definitive specification of when to apply this
8412 : // behavior, we'll base the decision on the HarfBuzz shaping engine
8413 : // used for each script: those that are handled by the Indic, Tibetan,
8414 : // Myanmar and SEAsian shapers will apply the "don't split ligatures"
8415 : // rule.
8416 :
8417 : // Indic
8418 : case Script::BENGALI:
8419 : case Script::DEVANAGARI:
8420 : case Script::GUJARATI:
8421 : case Script::GURMUKHI:
8422 : case Script::KANNADA:
8423 : case Script::MALAYALAM:
8424 : case Script::ORIYA:
8425 : case Script::TAMIL:
8426 : case Script::TELUGU:
8427 : case Script::SINHALA:
8428 : case Script::BALINESE:
8429 : case Script::LEPCHA:
8430 : case Script::REJANG:
8431 : case Script::SUNDANESE:
8432 : case Script::JAVANESE:
8433 : case Script::KAITHI:
8434 : case Script::MEETEI_MAYEK:
8435 : case Script::CHAKMA:
8436 : case Script::SHARADA:
8437 : case Script::TAKRI:
8438 : case Script::KHMER:
8439 :
8440 : // Tibetan
8441 : case Script::TIBETAN:
8442 :
8443 : // Myanmar
8444 : case Script::MYANMAR:
8445 :
8446 : // Other SEAsian
8447 : case Script::BUGINESE:
8448 : case Script::NEW_TAI_LUE:
8449 : case Script::CHAM:
8450 : case Script::TAI_THAM:
8451 :
8452 : // What about Thai/Lao - any special handling needed?
8453 : // Should we special-case Arabic lam-alef?
8454 :
8455 0 : allowSplitLigature = false;
8456 0 : break;
8457 : }
8458 :
8459 0 : iter.SetOriginalOffset(aOffset + i);
8460 0 : FindClusterEnd(aTextRun, endOffset, &iter, allowSplitLigature);
8461 :
8462 0 : i = iter.GetOriginalOffset() - aOffset;
8463 0 : if (i + 1 == length)
8464 : return true;
8465 :
8466 : // consume clusters that start with punctuation
8467 0 : i = FindEndOfPunctuationRun(aFrag, aTextRun, &iter, aOffset, i + 1, endOffset);
8468 0 : if (i < length)
8469 0 : *aLength = i;
8470 : return true;
8471 : }
8472 :
8473 : static uint32_t
8474 0 : FindStartAfterSkippingWhitespace(PropertyProvider* aProvider,
8475 : nsIFrame::InlineIntrinsicISizeData* aData,
8476 : const nsStyleText* aTextStyle,
8477 : gfxSkipCharsIterator* aIterator,
8478 : uint32_t aFlowEndInTextRun)
8479 : {
8480 0 : if (aData->mSkipWhitespace) {
8481 0 : while (aIterator->GetSkippedOffset() < aFlowEndInTextRun &&
8482 0 : IsTrimmableSpace(aProvider->GetFragment(), aIterator->GetOriginalOffset(), aTextStyle)) {
8483 0 : aIterator->AdvanceOriginal(1);
8484 : }
8485 : }
8486 0 : return aIterator->GetSkippedOffset();
8487 : }
8488 :
8489 : float
8490 0 : nsTextFrame::GetFontSizeInflation() const
8491 : {
8492 0 : if (!HasFontSizeInflation()) {
8493 : return 1.0f;
8494 : }
8495 0 : return GetProperty(FontSizeInflationProperty());
8496 : }
8497 :
8498 : void
8499 0 : nsTextFrame::SetFontSizeInflation(float aInflation)
8500 : {
8501 0 : if (aInflation == 1.0f) {
8502 0 : if (HasFontSizeInflation()) {
8503 0 : RemoveStateBits(TEXT_HAS_FONT_INFLATION);
8504 0 : DeleteProperty(FontSizeInflationProperty());
8505 : }
8506 : return;
8507 : }
8508 :
8509 0 : AddStateBits(TEXT_HAS_FONT_INFLATION);
8510 0 : SetProperty(FontSizeInflationProperty(), aInflation);
8511 : }
8512 :
8513 : /* virtual */
8514 0 : void nsTextFrame::MarkIntrinsicISizesDirty()
8515 : {
8516 0 : ClearTextRuns();
8517 0 : nsFrame::MarkIntrinsicISizesDirty();
8518 0 : }
8519 :
8520 : // XXX this doesn't handle characters shaped by line endings. We need to
8521 : // temporarily override the "current line ending" settings.
8522 : void
8523 0 : nsTextFrame::AddInlineMinISizeForFlow(gfxContext *aRenderingContext,
8524 : nsIFrame::InlineMinISizeData *aData,
8525 : TextRunType aTextRunType)
8526 : {
8527 : uint32_t flowEndInTextRun;
8528 : gfxSkipCharsIterator iter =
8529 : EnsureTextRun(aTextRunType, aRenderingContext->GetDrawTarget(),
8530 0 : aData->LineContainer(), aData->mLine, &flowEndInTextRun);
8531 0 : gfxTextRun *textRun = GetTextRun(aTextRunType);
8532 0 : if (!textRun)
8533 0 : return;
8534 :
8535 : // Pass null for the line container. This will disable tab spacing, but that's
8536 : // OK since we can't really handle tabs for intrinsic sizing anyway.
8537 0 : const nsStyleText* textStyle = StyleText();
8538 0 : const nsTextFragment* frag = mContent->GetText();
8539 :
8540 : // If we're hyphenating, the PropertyProvider needs the actual length;
8541 : // otherwise we can just pass INT32_MAX to mean "all the text"
8542 0 : int32_t len = INT32_MAX;
8543 0 : bool hyphenating = frag->GetLength() > 0 &&
8544 0 : (textStyle->mHyphens == StyleHyphens::Auto ||
8545 0 : (textStyle->mHyphens == StyleHyphens::Manual &&
8546 0 : !!(textRun->GetFlags() & gfx::ShapedTextFlags::TEXT_ENABLE_HYPHEN_BREAKS)));
8547 0 : if (hyphenating) {
8548 0 : gfxSkipCharsIterator tmp(iter);
8549 0 : len = std::min<int32_t>(GetContentOffset() + GetInFlowContentLength(),
8550 0 : tmp.ConvertSkippedToOriginal(flowEndInTextRun)) - iter.GetOriginalOffset();
8551 : }
8552 : PropertyProvider provider(textRun, textStyle, frag, this,
8553 0 : iter, len, nullptr, 0, aTextRunType);
8554 :
8555 0 : bool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant();
8556 0 : bool preformatNewlines = textStyle->NewlineIsSignificant(this);
8557 0 : bool preformatTabs = textStyle->WhiteSpaceIsSignificant();
8558 0 : gfxFloat tabWidth = -1;
8559 : uint32_t start =
8560 0 : FindStartAfterSkippingWhitespace(&provider, aData, textStyle, &iter, flowEndInTextRun);
8561 :
8562 : // text-combine-upright frame is constantly 1em on inline-axis.
8563 0 : if (Style()->IsTextCombined()) {
8564 0 : if (start < flowEndInTextRun && textRun->CanBreakLineBefore(start)) {
8565 0 : aData->OptionallyBreak();
8566 : }
8567 0 : aData->mCurrentLine += provider.GetFontMetrics()->EmHeight();
8568 0 : aData->mTrailingWhitespace = 0;
8569 0 : return;
8570 : }
8571 :
8572 0 : AutoTArray<gfxTextRun::HyphenType, BIG_TEXT_NODE_SIZE> hyphBuffer;
8573 0 : if (hyphenating) {
8574 0 : if (hyphBuffer.AppendElements(flowEndInTextRun - start, fallible)) {
8575 0 : provider.GetHyphenationBreaks(Range(start, flowEndInTextRun),
8576 0 : hyphBuffer.Elements());
8577 : } else {
8578 : hyphenating = false;
8579 : }
8580 : }
8581 :
8582 0 : for (uint32_t i = start, wordStart = start; i <= flowEndInTextRun; ++i) {
8583 0 : bool preformattedNewline = false;
8584 0 : bool preformattedTab = false;
8585 0 : if (i < flowEndInTextRun) {
8586 : // XXXldb Shouldn't we be including the newline as part of the
8587 : // segment that it ends rather than part of the segment that it
8588 : // starts?
8589 0 : preformattedNewline = preformatNewlines && textRun->CharIsNewline(i);
8590 0 : preformattedTab = preformatTabs && textRun->CharIsTab(i);
8591 0 : if (!textRun->CanBreakLineBefore(i) &&
8592 0 : !preformattedNewline &&
8593 0 : !preformattedTab &&
8594 0 : (!hyphenating ||
8595 0 : hyphBuffer[i - start] == gfxTextRun::HyphenType::None))
8596 : {
8597 : // we can't break here (and it's not the end of the flow)
8598 : continue;
8599 : }
8600 : }
8601 :
8602 0 : if (i > wordStart) {
8603 0 : nscoord width = NSToCoordCeilClamped(
8604 0 : textRun->GetAdvanceWidth(Range(wordStart, i), &provider));
8605 0 : width = std::max(0, width);
8606 0 : aData->mCurrentLine = NSCoordSaturatingAdd(aData->mCurrentLine, width);
8607 0 : aData->mAtStartOfLine = false;
8608 :
8609 0 : if (collapseWhitespace) {
8610 0 : uint32_t trimStart = GetEndOfTrimmedText(frag, textStyle, wordStart, i, &iter);
8611 0 : if (trimStart == start) {
8612 : // This is *all* trimmable whitespace, so whatever trailingWhitespace
8613 : // we saw previously is still trailing...
8614 0 : aData->mTrailingWhitespace += width;
8615 : } else {
8616 : // Some non-whitespace so the old trailingWhitespace is no longer trailing
8617 0 : nscoord wsWidth = NSToCoordCeilClamped(
8618 0 : textRun->GetAdvanceWidth(Range(trimStart, i), &provider));
8619 0 : aData->mTrailingWhitespace = std::max(0, wsWidth);
8620 : }
8621 : } else {
8622 0 : aData->mTrailingWhitespace = 0;
8623 : }
8624 : }
8625 :
8626 0 : if (preformattedTab) {
8627 : PropertyProvider::Spacing spacing;
8628 0 : provider.GetSpacing(Range(i, i + 1), &spacing);
8629 0 : aData->mCurrentLine += nscoord(spacing.mBefore);
8630 0 : if (tabWidth < 0) {
8631 0 : tabWidth = ComputeTabWidthAppUnits(this, textRun);
8632 : }
8633 : gfxFloat afterTab =
8634 0 : AdvanceToNextTab(aData->mCurrentLine, tabWidth);
8635 0 : aData->mCurrentLine = nscoord(afterTab + spacing.mAfter);
8636 0 : wordStart = i + 1;
8637 0 : } else if (i < flowEndInTextRun ||
8638 0 : (i == textRun->GetLength() &&
8639 0 : (textRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_HAS_TRAILING_BREAK))) {
8640 0 : if (preformattedNewline) {
8641 0 : aData->ForceBreak();
8642 0 : } else if (i < flowEndInTextRun && hyphenating &&
8643 0 : hyphBuffer[i - start] != gfxTextRun::HyphenType::None) {
8644 0 : aData->OptionallyBreak(NSToCoordRound(provider.GetHyphenWidth()));
8645 : } else {
8646 0 : aData->OptionallyBreak();
8647 : }
8648 : wordStart = i;
8649 : }
8650 : }
8651 :
8652 0 : if (start < flowEndInTextRun) {
8653 : // Check if we have collapsible whitespace at the end
8654 0 : aData->mSkipWhitespace =
8655 0 : IsTrimmableSpace(provider.GetFragment(),
8656 0 : iter.ConvertSkippedToOriginal(flowEndInTextRun - 1),
8657 : textStyle);
8658 : }
8659 : }
8660 :
8661 0 : bool nsTextFrame::IsCurrentFontInflation(float aInflation) const {
8662 0 : return fabsf(aInflation - GetFontSizeInflation()) < 1e-6;
8663 : }
8664 :
8665 : // XXX Need to do something here to avoid incremental reflow bugs due to
8666 : // first-line and first-letter changing min-width
8667 : /* virtual */ void
8668 0 : nsTextFrame::AddInlineMinISize(gfxContext *aRenderingContext,
8669 : nsIFrame::InlineMinISizeData *aData)
8670 : {
8671 0 : float inflation = nsLayoutUtils::FontSizeInflationFor(this);
8672 0 : TextRunType trtype = (inflation == 1.0f) ? eNotInflated : eInflated;
8673 :
8674 0 : if (trtype == eInflated && !IsCurrentFontInflation(inflation)) {
8675 : // FIXME: Ideally, if we already have a text run, we'd move it to be
8676 : // the uninflated text run.
8677 0 : ClearTextRun(nullptr, nsTextFrame::eInflated);
8678 : }
8679 :
8680 : nsTextFrame* f;
8681 : const gfxTextRun* lastTextRun = nullptr;
8682 : // nsContinuingTextFrame does nothing for AddInlineMinISize; all text frames
8683 : // in the flow are handled right here.
8684 0 : for (f = this; f; f = f->GetNextContinuation()) {
8685 : // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we
8686 : // haven't set up textruns yet for f. Except in OOM situations,
8687 : // lastTextRun will only be null for the first text frame.
8688 0 : if (f == this || f->GetTextRun(trtype) != lastTextRun) {
8689 : nsIFrame* lc;
8690 0 : if (aData->LineContainer() &&
8691 0 : aData->LineContainer() != (lc = FindLineContainer(f))) {
8692 0 : NS_ASSERTION(f != this, "wrong InlineMinISizeData container"
8693 : " for first continuation");
8694 0 : aData->mLine = nullptr;
8695 0 : aData->SetLineContainer(lc);
8696 : }
8697 :
8698 : // This will process all the text frames that share the same textrun as f.
8699 0 : f->AddInlineMinISizeForFlow(aRenderingContext, aData, trtype);
8700 0 : lastTextRun = f->GetTextRun(trtype);
8701 : }
8702 : }
8703 0 : }
8704 :
8705 : // XXX this doesn't handle characters shaped by line endings. We need to
8706 : // temporarily override the "current line ending" settings.
8707 : void
8708 0 : nsTextFrame::AddInlinePrefISizeForFlow(gfxContext *aRenderingContext,
8709 : nsIFrame::InlinePrefISizeData *aData,
8710 : TextRunType aTextRunType)
8711 : {
8712 : uint32_t flowEndInTextRun;
8713 : gfxSkipCharsIterator iter =
8714 : EnsureTextRun(aTextRunType, aRenderingContext->GetDrawTarget(),
8715 0 : aData->LineContainer(), aData->mLine, &flowEndInTextRun);
8716 0 : gfxTextRun *textRun = GetTextRun(aTextRunType);
8717 0 : if (!textRun)
8718 0 : return;
8719 :
8720 : // Pass null for the line container. This will disable tab spacing, but that's
8721 : // OK since we can't really handle tabs for intrinsic sizing anyway.
8722 :
8723 0 : const nsStyleText* textStyle = StyleText();
8724 0 : const nsTextFragment* frag = mContent->GetText();
8725 : PropertyProvider provider(textRun, textStyle, frag, this,
8726 0 : iter, INT32_MAX, nullptr, 0, aTextRunType);
8727 :
8728 : // text-combine-upright frame is constantly 1em on inline-axis.
8729 0 : if (Style()->IsTextCombined()) {
8730 0 : aData->mCurrentLine += provider.GetFontMetrics()->EmHeight();
8731 0 : aData->mTrailingWhitespace = 0;
8732 0 : aData->mLineIsEmpty = false;
8733 0 : return;
8734 : }
8735 :
8736 0 : bool collapseWhitespace = !textStyle->WhiteSpaceIsSignificant();
8737 0 : bool preformatNewlines = textStyle->NewlineIsSignificant(this);
8738 0 : bool preformatTabs = textStyle->TabIsSignificant();
8739 0 : gfxFloat tabWidth = -1;
8740 : uint32_t start =
8741 0 : FindStartAfterSkippingWhitespace(&provider, aData, textStyle, &iter, flowEndInTextRun);
8742 :
8743 : // XXX Should we consider hyphenation here?
8744 : // If newlines and tabs aren't preformatted, nothing to do inside
8745 : // the loop so make i skip to the end
8746 0 : uint32_t loopStart = (preformatNewlines || preformatTabs) ? start : flowEndInTextRun;
8747 0 : for (uint32_t i = loopStart, lineStart = start; i <= flowEndInTextRun; ++i) {
8748 0 : bool preformattedNewline = false;
8749 0 : bool preformattedTab = false;
8750 0 : if (i < flowEndInTextRun) {
8751 : // XXXldb Shouldn't we be including the newline as part of the
8752 : // segment that it ends rather than part of the segment that it
8753 : // starts?
8754 0 : NS_ASSERTION(preformatNewlines || preformatTabs,
8755 : "We can't be here unless newlines are "
8756 : "hard breaks or there are tabs");
8757 0 : preformattedNewline = preformatNewlines && textRun->CharIsNewline(i);
8758 0 : preformattedTab = preformatTabs && textRun->CharIsTab(i);
8759 0 : if (!preformattedNewline && !preformattedTab) {
8760 : // we needn't break here (and it's not the end of the flow)
8761 : continue;
8762 : }
8763 : }
8764 :
8765 0 : if (i > lineStart) {
8766 0 : nscoord width = NSToCoordCeilClamped(
8767 0 : textRun->GetAdvanceWidth(Range(lineStart, i), &provider));
8768 0 : width = std::max(0, width);
8769 0 : aData->mCurrentLine = NSCoordSaturatingAdd(aData->mCurrentLine, width);
8770 0 : aData->mLineIsEmpty = false;
8771 :
8772 0 : if (collapseWhitespace) {
8773 0 : uint32_t trimStart = GetEndOfTrimmedText(frag, textStyle, lineStart, i, &iter);
8774 0 : if (trimStart == start) {
8775 : // This is *all* trimmable whitespace, so whatever trailingWhitespace
8776 : // we saw previously is still trailing...
8777 0 : aData->mTrailingWhitespace += width;
8778 : } else {
8779 : // Some non-whitespace so the old trailingWhitespace is no longer trailing
8780 0 : nscoord wsWidth = NSToCoordCeilClamped(
8781 0 : textRun->GetAdvanceWidth(Range(trimStart, i), &provider));
8782 0 : aData->mTrailingWhitespace = std::max(0, wsWidth);
8783 : }
8784 : } else {
8785 0 : aData->mTrailingWhitespace = 0;
8786 : }
8787 : }
8788 :
8789 0 : if (preformattedTab) {
8790 : PropertyProvider::Spacing spacing;
8791 0 : provider.GetSpacing(Range(i, i + 1), &spacing);
8792 0 : aData->mCurrentLine += nscoord(spacing.mBefore);
8793 0 : if (tabWidth < 0) {
8794 0 : tabWidth = ComputeTabWidthAppUnits(this, textRun);
8795 : }
8796 : gfxFloat afterTab =
8797 0 : AdvanceToNextTab(aData->mCurrentLine, tabWidth);
8798 0 : aData->mCurrentLine = nscoord(afterTab + spacing.mAfter);
8799 0 : aData->mLineIsEmpty = false;
8800 0 : lineStart = i + 1;
8801 0 : } else if (preformattedNewline) {
8802 0 : aData->ForceBreak();
8803 0 : lineStart = i;
8804 : }
8805 : }
8806 :
8807 : // Check if we have collapsible whitespace at the end
8808 0 : if (start < flowEndInTextRun) {
8809 0 : aData->mSkipWhitespace =
8810 0 : IsTrimmableSpace(provider.GetFragment(),
8811 0 : iter.ConvertSkippedToOriginal(flowEndInTextRun - 1),
8812 : textStyle);
8813 : }
8814 : }
8815 :
8816 : // XXX Need to do something here to avoid incremental reflow bugs due to
8817 : // first-line and first-letter changing pref-width
8818 : /* virtual */ void
8819 0 : nsTextFrame::AddInlinePrefISize(gfxContext *aRenderingContext,
8820 : nsIFrame::InlinePrefISizeData *aData)
8821 : {
8822 0 : float inflation = nsLayoutUtils::FontSizeInflationFor(this);
8823 0 : TextRunType trtype = (inflation == 1.0f) ? eNotInflated : eInflated;
8824 :
8825 0 : if (trtype == eInflated && !IsCurrentFontInflation(inflation)) {
8826 : // FIXME: Ideally, if we already have a text run, we'd move it to be
8827 : // the uninflated text run.
8828 0 : ClearTextRun(nullptr, nsTextFrame::eInflated);
8829 : }
8830 :
8831 : nsTextFrame* f;
8832 : const gfxTextRun* lastTextRun = nullptr;
8833 : // nsContinuingTextFrame does nothing for AddInlineMinISize; all text frames
8834 : // in the flow are handled right here.
8835 0 : for (f = this; f; f = f->GetNextContinuation()) {
8836 : // f->GetTextRun(nsTextFrame::eNotInflated) could be null if we
8837 : // haven't set up textruns yet for f. Except in OOM situations,
8838 : // lastTextRun will only be null for the first text frame.
8839 0 : if (f == this || f->GetTextRun(trtype) != lastTextRun) {
8840 : nsIFrame* lc;
8841 0 : if (aData->LineContainer() &&
8842 0 : aData->LineContainer() != (lc = FindLineContainer(f))) {
8843 0 : NS_ASSERTION(f != this, "wrong InlinePrefISizeData container"
8844 : " for first continuation");
8845 0 : aData->mLine = nullptr;
8846 0 : aData->SetLineContainer(lc);
8847 : }
8848 :
8849 : // This will process all the text frames that share the same textrun as f.
8850 0 : f->AddInlinePrefISizeForFlow(aRenderingContext, aData, trtype);
8851 0 : lastTextRun = f->GetTextRun(trtype);
8852 : }
8853 : }
8854 0 : }
8855 :
8856 : /* virtual */
8857 : LogicalSize
8858 0 : nsTextFrame::ComputeSize(gfxContext *aRenderingContext,
8859 : WritingMode aWM,
8860 : const LogicalSize& aCBSize,
8861 : nscoord aAvailableISize,
8862 : const LogicalSize& aMargin,
8863 : const LogicalSize& aBorder,
8864 : const LogicalSize& aPadding,
8865 : ComputeSizeFlags aFlags)
8866 : {
8867 : // Inlines and text don't compute size before reflow.
8868 0 : return LogicalSize(aWM, NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
8869 : }
8870 :
8871 : static nsRect
8872 0 : RoundOut(const gfxRect& aRect)
8873 : {
8874 0 : nsRect r;
8875 0 : r.x = NSToCoordFloor(aRect.X());
8876 0 : r.y = NSToCoordFloor(aRect.Y());
8877 0 : r.width = NSToCoordCeil(aRect.XMost()) - r.x;
8878 0 : r.height = NSToCoordCeil(aRect.YMost()) - r.y;
8879 0 : return r;
8880 : }
8881 :
8882 : nsRect
8883 0 : nsTextFrame::ComputeTightBounds(DrawTarget* aDrawTarget) const
8884 : {
8885 0 : if (Style()->HasTextDecorationLines() ||
8886 0 : (GetStateBits() & TEXT_HYPHEN_BREAK)) {
8887 : // This is conservative, but OK.
8888 0 : return GetVisualOverflowRect();
8889 : }
8890 :
8891 : gfxSkipCharsIterator iter =
8892 0 : const_cast<nsTextFrame*>(this)->EnsureTextRun(nsTextFrame::eInflated);
8893 0 : if (!mTextRun)
8894 : return nsRect(0, 0, 0, 0);
8895 :
8896 : PropertyProvider provider(const_cast<nsTextFrame*>(this), iter,
8897 0 : nsTextFrame::eInflated);
8898 : // Trim trailing whitespace
8899 0 : provider.InitializeForDisplay(true);
8900 :
8901 : gfxTextRun::Metrics metrics =
8902 : mTextRun->MeasureText(ComputeTransformedRange(provider),
8903 : gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS,
8904 0 : aDrawTarget, &provider);
8905 0 : if (GetWritingMode().IsLineInverted()) {
8906 0 : metrics.mBoundingBox.y = -metrics.mBoundingBox.YMost();
8907 : }
8908 : // mAscent should be the same as metrics.mAscent, but it's what we use to
8909 : // paint so that's the one we'll use.
8910 0 : nsRect boundingBox = RoundOut(metrics.mBoundingBox);
8911 0 : boundingBox += nsPoint(0, mAscent);
8912 0 : if (mTextRun->IsVertical()) {
8913 : // Swap line-relative textMetrics dimensions to physical coordinates.
8914 0 : Swap(boundingBox.x, boundingBox.y);
8915 : Swap(boundingBox.width, boundingBox.height);
8916 : }
8917 0 : return boundingBox;
8918 : }
8919 :
8920 : /* virtual */ nsresult
8921 0 : nsTextFrame::GetPrefWidthTightBounds(gfxContext* aContext,
8922 : nscoord* aX,
8923 : nscoord* aXMost)
8924 : {
8925 : gfxSkipCharsIterator iter =
8926 0 : const_cast<nsTextFrame*>(this)->EnsureTextRun(nsTextFrame::eInflated);
8927 0 : if (!mTextRun)
8928 : return NS_ERROR_FAILURE;
8929 :
8930 : PropertyProvider provider(const_cast<nsTextFrame*>(this), iter,
8931 0 : nsTextFrame::eInflated);
8932 0 : provider.InitializeForMeasure();
8933 :
8934 : gfxTextRun::Metrics metrics =
8935 : mTextRun->MeasureText(ComputeTransformedRange(provider),
8936 : gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS,
8937 0 : aContext->GetDrawTarget(), &provider);
8938 : // Round it like nsTextFrame::ComputeTightBounds() to ensure consistency.
8939 0 : *aX = NSToCoordFloor(metrics.mBoundingBox.x);
8940 0 : *aXMost = NSToCoordCeil(metrics.mBoundingBox.XMost());
8941 :
8942 : return NS_OK;
8943 : }
8944 :
8945 : static bool
8946 0 : HasSoftHyphenBefore(const nsTextFragment* aFrag, const gfxTextRun* aTextRun,
8947 : int32_t aStartOffset, const gfxSkipCharsIterator& aIter)
8948 : {
8949 0 : if (aIter.GetSkippedOffset() < aTextRun->GetLength() &&
8950 0 : aTextRun->CanHyphenateBefore(aIter.GetSkippedOffset())) {
8951 : return true;
8952 : }
8953 0 : if (!(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_HAS_SHY))
8954 : return false;
8955 0 : gfxSkipCharsIterator iter = aIter;
8956 0 : while (iter.GetOriginalOffset() > aStartOffset) {
8957 0 : iter.AdvanceOriginal(-1);
8958 0 : if (!iter.IsOriginalCharSkipped())
8959 : break;
8960 0 : if (aFrag->CharAt(iter.GetOriginalOffset()) == CH_SHY)
8961 : return true;
8962 : }
8963 : return false;
8964 : }
8965 :
8966 : /**
8967 : * Removes all frames from aFrame up to (but not including) aFirstToNotRemove,
8968 : * because their text has all been taken and reflowed by earlier frames.
8969 : */
8970 : static void
8971 0 : RemoveEmptyInFlows(nsTextFrame* aFrame, nsTextFrame* aFirstToNotRemove)
8972 : {
8973 0 : MOZ_ASSERT(aFrame != aFirstToNotRemove, "This will go very badly");
8974 : // We have to be careful here, because some RemoveFrame implementations
8975 : // remove and destroy not only the passed-in frame but also all its following
8976 : // in-flows (and sometimes all its following continuations in general). So
8977 : // we remove |f| and everything up to but not including firstToNotRemove from
8978 : // the flow first, to make sure that only the things we want destroyed are
8979 : // destroyed.
8980 :
8981 : // This sadly duplicates some of the logic from
8982 : // nsSplittableFrame::RemoveFromFlow. We can get away with not duplicating
8983 : // all of it, because we know that the prev-continuation links of
8984 : // firstToNotRemove and f are fluid, and non-null.
8985 0 : NS_ASSERTION(aFirstToNotRemove->GetPrevContinuation() ==
8986 : aFirstToNotRemove->GetPrevInFlow() &&
8987 : aFirstToNotRemove->GetPrevInFlow() != nullptr,
8988 : "aFirstToNotRemove should have a fluid prev continuation");
8989 0 : NS_ASSERTION(aFrame->GetPrevContinuation() ==
8990 : aFrame->GetPrevInFlow() &&
8991 : aFrame->GetPrevInFlow() != nullptr,
8992 : "aFrame should have a fluid prev continuation");
8993 :
8994 0 : nsTextFrame* prevContinuation = aFrame->GetPrevContinuation();
8995 0 : nsTextFrame* lastRemoved = aFirstToNotRemove->GetPrevContinuation();
8996 :
8997 0 : for (nsTextFrame* f = aFrame; f != aFirstToNotRemove;
8998 : f = f->GetNextContinuation()) {
8999 : // f is going to be destroyed soon, after it is unlinked from the
9000 : // continuation chain. If its textrun is going to be destroyed we need to
9001 : // do it now, before we unlink the frames to remove from the flow,
9002 : // because DestroyFrom calls ClearTextRuns() and that will start at the
9003 : // first frame with the text run and walk the continuations.
9004 0 : if (f->IsInTextRunUserData()) {
9005 0 : f->ClearTextRuns();
9006 : } else {
9007 0 : f->DisconnectTextRuns();
9008 : }
9009 : }
9010 :
9011 0 : prevContinuation->SetNextInFlow(aFirstToNotRemove);
9012 0 : aFirstToNotRemove->SetPrevInFlow(prevContinuation);
9013 :
9014 0 : aFrame->SetPrevInFlow(nullptr);
9015 0 : lastRemoved->SetNextInFlow(nullptr);
9016 :
9017 0 : nsContainerFrame* parent = aFrame->GetParent();
9018 0 : nsBlockFrame* parentBlock = nsLayoutUtils::GetAsBlock(parent);
9019 0 : if (parentBlock) {
9020 : // Manually call DoRemoveFrame so we can tell it that we're
9021 : // removing empty frames; this will keep it from blowing away
9022 : // text runs.
9023 0 : parentBlock->DoRemoveFrame(aFrame, nsBlockFrame::FRAMES_ARE_EMPTY);
9024 : } else {
9025 : // Just remove it normally; use kNoReflowPrincipalList to avoid posting
9026 : // new reflows.
9027 0 : parent->RemoveFrame(nsIFrame::kNoReflowPrincipalList, aFrame);
9028 : }
9029 0 : }
9030 :
9031 : void
9032 0 : nsTextFrame::SetLength(int32_t aLength, nsLineLayout* aLineLayout,
9033 : uint32_t aSetLengthFlags)
9034 : {
9035 0 : mContentLengthHint = aLength;
9036 0 : int32_t end = GetContentOffset() + aLength;
9037 0 : nsTextFrame* f = GetNextInFlow();
9038 0 : if (!f)
9039 : return;
9040 :
9041 : // If our end offset is moving, then even if frames are not being pushed or
9042 : // pulled, content is moving to or from the next line and the next line
9043 : // must be reflowed.
9044 : // If the next-continuation is dirty, then we should dirty the next line now
9045 : // because we may have skipped doing it if we dirtied it in
9046 : // CharacterDataChanged. This is ugly but teaching FrameNeedsReflow
9047 : // and ChildIsDirty to handle a range of frames would be worse.
9048 0 : if (aLineLayout &&
9049 0 : (end != f->mContentOffset || (f->GetStateBits() & NS_FRAME_IS_DIRTY))) {
9050 : aLineLayout->SetDirtyNextLine();
9051 : }
9052 :
9053 0 : if (end < f->mContentOffset) {
9054 : // Our frame is shrinking. Give the text to our next in flow.
9055 0 : if (aLineLayout && HasSignificantTerminalNewline() &&
9056 0 : !GetParent()->IsLetterFrame() &&
9057 0 : (aSetLengthFlags & ALLOW_FRAME_CREATION_AND_DESTRUCTION)) {
9058 : // Whatever text we hand to our next-in-flow will end up in a frame all of
9059 : // its own, since it ends in a forced linebreak. Might as well just put
9060 : // it in a separate frame now. This is important to prevent text run
9061 : // churn; if we did not do that, then we'd likely end up rebuilding
9062 : // textruns for all our following continuations.
9063 : // We skip this optimization when the parent is a first-letter frame
9064 : // because it doesn't deal well with more than one child frame.
9065 : // We also skip this optimization if we were called during bidi
9066 : // resolution, so as not to create a new frame which doesn't appear in
9067 : // the bidi resolver's list of frames
9068 0 : nsPresContext* presContext = PresContext();
9069 : nsIFrame* newFrame = presContext->PresShell()->FrameConstructor()->
9070 0 : CreateContinuingFrame(presContext, this, GetParent());
9071 0 : nsTextFrame* next = static_cast<nsTextFrame*>(newFrame);
9072 0 : nsFrameList temp(next, next);
9073 0 : GetParent()->InsertFrames(kNoReflowPrincipalList, this, temp);
9074 0 : f = next;
9075 : }
9076 :
9077 0 : f->mContentOffset = end;
9078 0 : if (f->GetTextRun(nsTextFrame::eInflated) != mTextRun) {
9079 0 : ClearTextRuns();
9080 0 : f->ClearTextRuns();
9081 : }
9082 : return;
9083 : }
9084 : // Our frame is growing. Take text from our in-flow(s).
9085 : // We can take text from frames in lines beyond just the next line.
9086 : // We don't dirty those lines. That's OK, because when we reflow
9087 : // our empty next-in-flow, it will take text from its next-in-flow and
9088 : // dirty that line.
9089 :
9090 : // Note that in the process we may end up removing some frames from
9091 : // the flow if they end up empty.
9092 : nsTextFrame* framesToRemove = nullptr;
9093 0 : while (f && f->mContentOffset < end) {
9094 0 : f->mContentOffset = end;
9095 0 : if (f->GetTextRun(nsTextFrame::eInflated) != mTextRun) {
9096 0 : ClearTextRuns();
9097 0 : f->ClearTextRuns();
9098 : }
9099 0 : nsTextFrame* next = f->GetNextInFlow();
9100 : // Note: the "f->GetNextSibling() == next" check below is to restrict
9101 : // this optimization to the case where they are on the same child list.
9102 : // Otherwise we might remove the only child of a nsFirstLetterFrame
9103 : // for example and it can't handle that. See bug 597627 for details.
9104 0 : if (next && next->mContentOffset <= end && f->GetNextSibling() == next &&
9105 0 : (aSetLengthFlags & ALLOW_FRAME_CREATION_AND_DESTRUCTION)) {
9106 : // |f| is now empty. We may as well remove it, instead of copying all
9107 : // the text from |next| into it instead; the latter leads to use
9108 : // rebuilding textruns for all following continuations.
9109 : // We skip this optimization if we were called during bidi resolution,
9110 : // since the bidi resolver may try to handle the destroyed frame later
9111 : // and crash
9112 0 : if (!framesToRemove) {
9113 : // Remember that we have to remove this frame.
9114 0 : framesToRemove = f;
9115 : }
9116 0 : } else if (framesToRemove) {
9117 0 : RemoveEmptyInFlows(framesToRemove, f);
9118 0 : framesToRemove = nullptr;
9119 : }
9120 : f = next;
9121 : }
9122 :
9123 0 : MOZ_ASSERT(!framesToRemove || (f && f->mContentOffset == end),
9124 : "How did we exit the loop if we null out framesToRemove if "
9125 : "!next || next->mContentOffset > end ?");
9126 :
9127 0 : if (framesToRemove) {
9128 : // We are guaranteed that we exited the loop with f not null, per the
9129 : // postcondition above
9130 0 : RemoveEmptyInFlows(framesToRemove, f);
9131 : }
9132 :
9133 : #ifdef DEBUG
9134 : f = this;
9135 : int32_t iterations = 0;
9136 0 : while (f && iterations < 10) {
9137 0 : f->GetContentLength(); // Assert if negative length
9138 0 : f = f->GetNextContinuation();
9139 0 : ++iterations;
9140 : }
9141 : f = this;
9142 : iterations = 0;
9143 0 : while (f && iterations < 10) {
9144 0 : f->GetContentLength(); // Assert if negative length
9145 0 : f = f->GetPrevContinuation();
9146 0 : ++iterations;
9147 : }
9148 : #endif
9149 : }
9150 :
9151 : bool
9152 0 : nsTextFrame::IsFloatingFirstLetterChild() const
9153 : {
9154 0 : nsIFrame* frame = GetParent();
9155 0 : return frame && frame->IsFloating() && frame->IsLetterFrame();
9156 : }
9157 :
9158 : bool
9159 0 : nsTextFrame::IsInitialLetterChild() const
9160 : {
9161 0 : nsIFrame* frame = GetParent();
9162 0 : return frame && frame->StyleTextReset()->mInitialLetterSize != 0.0f &&
9163 0 : frame->IsLetterFrame();
9164 : }
9165 :
9166 : struct NewlineProperty {
9167 : int32_t mStartOffset;
9168 : // The offset of the first \n after mStartOffset, or -1 if there is none
9169 : int32_t mNewlineOffset;
9170 : };
9171 :
9172 : void
9173 0 : nsTextFrame::Reflow(nsPresContext* aPresContext,
9174 : ReflowOutput& aMetrics,
9175 : const ReflowInput& aReflowInput,
9176 : nsReflowStatus& aStatus)
9177 : {
9178 0 : MarkInReflow();
9179 0 : DO_GLOBAL_REFLOW_COUNT("nsTextFrame");
9180 0 : DISPLAY_REFLOW(aPresContext, this, aReflowInput, aMetrics, aStatus);
9181 0 : MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
9182 :
9183 : // XXX If there's no line layout, we shouldn't even have created this
9184 : // frame. This may happen if, for example, this is text inside a table
9185 : // but not inside a cell. For now, just don't reflow.
9186 0 : if (!aReflowInput.mLineLayout) {
9187 0 : ClearMetrics(aMetrics);
9188 0 : return;
9189 : }
9190 :
9191 0 : ReflowText(*aReflowInput.mLineLayout, aReflowInput.AvailableWidth(),
9192 0 : aReflowInput.mRenderingContext->GetDrawTarget(), aMetrics, aStatus);
9193 :
9194 0 : NS_FRAME_SET_TRUNCATION(aStatus, aReflowInput, aMetrics);
9195 : }
9196 :
9197 : #ifdef ACCESSIBILITY
9198 : /**
9199 : * Notifies accessibility about text reflow. Used by nsTextFrame::ReflowText.
9200 : */
9201 : class MOZ_STACK_CLASS ReflowTextA11yNotifier
9202 : {
9203 : public:
9204 0 : ReflowTextA11yNotifier(nsPresContext* aPresContext, nsIContent* aContent) :
9205 0 : mContent(aContent), mPresContext(aPresContext)
9206 : {
9207 : }
9208 0 : ~ReflowTextA11yNotifier()
9209 0 : {
9210 0 : nsAccessibilityService* accService = nsIPresShell::AccService();
9211 0 : if (accService) {
9212 0 : accService->UpdateText(mPresContext->PresShell(), mContent);
9213 : }
9214 0 : }
9215 : private:
9216 : ReflowTextA11yNotifier();
9217 : ReflowTextA11yNotifier(const ReflowTextA11yNotifier&);
9218 : ReflowTextA11yNotifier& operator =(const ReflowTextA11yNotifier&);
9219 :
9220 : nsIContent* mContent;
9221 : nsPresContext* mPresContext;
9222 : };
9223 : #endif
9224 :
9225 : void
9226 0 : nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth,
9227 : DrawTarget* aDrawTarget,
9228 : ReflowOutput& aMetrics,
9229 : nsReflowStatus& aStatus)
9230 : {
9231 0 : MOZ_ASSERT(aStatus.IsEmpty(), "Caller should pass a fresh reflow status!");
9232 :
9233 : #ifdef NOISY_REFLOW
9234 : ListTag(stdout);
9235 : printf(": BeginReflow: availableWidth=%d\n", aAvailableWidth);
9236 : #endif
9237 :
9238 0 : nsPresContext* presContext = PresContext();
9239 :
9240 : #ifdef ACCESSIBILITY
9241 : // Schedule the update of accessible tree since rendered text might be changed.
9242 0 : if (StyleVisibility()->IsVisible()) {
9243 0 : ReflowTextA11yNotifier(presContext, mContent);
9244 : }
9245 : #endif
9246 :
9247 : /////////////////////////////////////////////////////////////////////
9248 : // Set up flags and clear out state
9249 : /////////////////////////////////////////////////////////////////////
9250 :
9251 : // Clear out the reflow state flags in mState. We also clear the whitespace
9252 : // flags because this can change whether the frame maps whitespace-only text
9253 : // or not. We also clear the flag that tracks whether we had a pending
9254 : // reflow request from CharacterDataChanged (since we're reflowing now).
9255 0 : RemoveStateBits(TEXT_REFLOW_FLAGS | TEXT_WHITESPACE_FLAGS);
9256 0 : mReflowRequestedForCharDataChange = false;
9257 :
9258 : // Temporarily map all possible content while we construct our new textrun.
9259 : // so that when doing reflow our styles prevail over any part of the
9260 : // textrun we look at. Note that next-in-flows may be mapping the same
9261 : // content; gfxTextRun construction logic will ensure that we take priority.
9262 0 : int32_t maxContentLength = GetInFlowContentLength();
9263 :
9264 : // We don't need to reflow if there is no content.
9265 0 : if (!maxContentLength) {
9266 0 : ClearMetrics(aMetrics);
9267 0 : return;
9268 : }
9269 :
9270 : #ifdef NOISY_BIDI
9271 : printf("Reflowed textframe\n");
9272 : #endif
9273 :
9274 0 : const nsStyleText* textStyle = StyleText();
9275 :
9276 0 : bool atStartOfLine = aLineLayout.LineAtStart();
9277 0 : if (atStartOfLine) {
9278 0 : AddStateBits(TEXT_START_OF_LINE);
9279 : }
9280 :
9281 : uint32_t flowEndInTextRun;
9282 0 : nsIFrame* lineContainer = aLineLayout.LineContainerFrame();
9283 0 : const nsTextFragment* frag = mContent->GetText();
9284 :
9285 : // DOM offsets of the text range we need to measure, after trimming
9286 : // whitespace, restricting to first-letter, and restricting preformatted text
9287 : // to nearest newline
9288 0 : int32_t length = maxContentLength;
9289 0 : int32_t offset = GetContentOffset();
9290 :
9291 : // Restrict preformatted text to the nearest newline
9292 0 : int32_t newLineOffset = -1; // this will be -1 or a content offset
9293 0 : int32_t contentNewLineOffset = -1;
9294 : // Pointer to the nsGkAtoms::newline set on this frame's element
9295 0 : NewlineProperty* cachedNewlineOffset = nullptr;
9296 0 : if (textStyle->NewlineIsSignificant(this)) {
9297 0 : cachedNewlineOffset =
9298 0 : mContent->HasFlag(NS_HAS_NEWLINE_PROPERTY)
9299 0 : ? static_cast<NewlineProperty*>(mContent->GetProperty(nsGkAtoms::newline))
9300 : : nullptr;
9301 0 : if (cachedNewlineOffset && cachedNewlineOffset->mStartOffset <= offset &&
9302 0 : (cachedNewlineOffset->mNewlineOffset == -1 ||
9303 : cachedNewlineOffset->mNewlineOffset >= offset)) {
9304 : contentNewLineOffset = cachedNewlineOffset->mNewlineOffset;
9305 : } else {
9306 0 : contentNewLineOffset = FindChar(frag, offset,
9307 0 : mContent->TextLength() - offset, '\n');
9308 : }
9309 0 : if (contentNewLineOffset < offset + length) {
9310 : /*
9311 : The new line offset could be outside this frame if the frame has been
9312 : split by bidi resolution. In that case we won't use it in this reflow
9313 : (newLineOffset will remain -1), but we will still cache it in mContent
9314 : */
9315 0 : newLineOffset = contentNewLineOffset;
9316 : }
9317 0 : if (newLineOffset >= 0) {
9318 0 : length = newLineOffset + 1 - offset;
9319 : }
9320 : }
9321 0 : if ((atStartOfLine && !textStyle->WhiteSpaceIsSignificant()) ||
9322 0 : (GetStateBits() & TEXT_IS_IN_TOKEN_MATHML)) {
9323 : // Skip leading whitespace. Make sure we don't skip a 'pre-line'
9324 : // newline if there is one.
9325 0 : int32_t skipLength = newLineOffset >= 0 ? length - 1 : length;
9326 : int32_t whitespaceCount =
9327 0 : GetTrimmableWhitespaceCount(frag, offset, skipLength, 1);
9328 0 : if (whitespaceCount) {
9329 0 : offset += whitespaceCount;
9330 0 : length -= whitespaceCount;
9331 : // Make sure this frame maps the trimmable whitespace.
9332 0 : if (MOZ_UNLIKELY(offset > GetContentEnd())) {
9333 0 : SetLength(offset - GetContentOffset(), &aLineLayout,
9334 0 : ALLOW_FRAME_CREATION_AND_DESTRUCTION);
9335 : }
9336 : }
9337 : }
9338 :
9339 : // If trimming whitespace left us with nothing to do, return early.
9340 0 : if (length == 0) {
9341 0 : ClearMetrics(aMetrics);
9342 0 : return;
9343 : }
9344 :
9345 0 : bool completedFirstLetter = false;
9346 : // Layout dependent styles are a problem because we need to reconstruct
9347 : // the gfxTextRun based on our layout.
9348 0 : if (aLineLayout.GetInFirstLetter() || aLineLayout.GetInFirstLine()) {
9349 : SetLength(maxContentLength, &aLineLayout,
9350 0 : ALLOW_FRAME_CREATION_AND_DESTRUCTION);
9351 :
9352 0 : if (aLineLayout.GetInFirstLetter()) {
9353 : // floating first-letter boundaries are significant in textrun
9354 : // construction, so clear the textrun out every time we hit a first-letter
9355 : // and have changed our length (which controls the first-letter boundary)
9356 0 : ClearTextRuns();
9357 : // Find the length of the first-letter. We need a textrun for this.
9358 : // REVIEW: maybe-bogus inflation should be ok (fixed below)
9359 : gfxSkipCharsIterator iter =
9360 : EnsureTextRun(nsTextFrame::eInflated, aDrawTarget,
9361 0 : lineContainer, aLineLayout.GetLine(),
9362 0 : &flowEndInTextRun);
9363 :
9364 0 : if (mTextRun) {
9365 0 : int32_t firstLetterLength = length;
9366 0 : if (aLineLayout.GetFirstLetterStyleOK()) {
9367 : completedFirstLetter =
9368 0 : FindFirstLetterRange(frag, mTextRun, offset, iter, &firstLetterLength);
9369 0 : if (newLineOffset >= 0) {
9370 : // Don't allow a preformatted newline to be part of a first-letter.
9371 0 : firstLetterLength = std::min(firstLetterLength, length - 1);
9372 0 : if (length == 1) {
9373 : // There is no text to be consumed by the first-letter before the
9374 : // preformatted newline. Note that the first letter is therefore
9375 : // complete (FindFirstLetterRange will have returned false).
9376 0 : completedFirstLetter = true;
9377 : }
9378 : }
9379 : } else {
9380 : // We're in a first-letter frame's first in flow, so if there
9381 : // was a first-letter, we'd be it. However, for one reason
9382 : // or another (e.g., preformatted line break before this text),
9383 : // we're not actually supposed to have first-letter style. So
9384 : // just make a zero-length first-letter.
9385 0 : firstLetterLength = 0;
9386 0 : completedFirstLetter = true;
9387 : }
9388 0 : length = firstLetterLength;
9389 0 : if (length) {
9390 0 : AddStateBits(TEXT_FIRST_LETTER);
9391 : }
9392 : // Change this frame's length to the first-letter length right now
9393 : // so that when we rebuild the textrun it will be built with the
9394 : // right first-letter boundary
9395 0 : SetLength(offset + length - GetContentOffset(), &aLineLayout,
9396 0 : ALLOW_FRAME_CREATION_AND_DESTRUCTION);
9397 : // Ensure that the textrun will be rebuilt
9398 0 : ClearTextRuns();
9399 : }
9400 : }
9401 : }
9402 :
9403 0 : float fontSizeInflation = nsLayoutUtils::FontSizeInflationFor(this);
9404 :
9405 0 : if (!IsCurrentFontInflation(fontSizeInflation)) {
9406 : // FIXME: Ideally, if we already have a text run, we'd move it to be
9407 : // the uninflated text run.
9408 0 : ClearTextRun(nullptr, nsTextFrame::eInflated);
9409 : }
9410 :
9411 : gfxSkipCharsIterator iter =
9412 : EnsureTextRun(nsTextFrame::eInflated, aDrawTarget,
9413 0 : lineContainer, aLineLayout.GetLine(), &flowEndInTextRun);
9414 :
9415 0 : NS_ASSERTION(IsCurrentFontInflation(fontSizeInflation),
9416 : "EnsureTextRun should have set font size inflation");
9417 :
9418 0 : if (mTextRun && iter.GetOriginalEnd() < offset + length) {
9419 : // The textrun does not map enough text for this frame. This can happen
9420 : // when the textrun was ended in the middle of a text node because a
9421 : // preformatted newline was encountered, and prev-in-flow frames have
9422 : // consumed all the text of the textrun. We need a new textrun.
9423 0 : ClearTextRuns();
9424 0 : iter = EnsureTextRun(nsTextFrame::eInflated, aDrawTarget,
9425 0 : lineContainer, aLineLayout.GetLine(),
9426 : &flowEndInTextRun);
9427 : }
9428 :
9429 0 : if (!mTextRun) {
9430 0 : ClearMetrics(aMetrics);
9431 0 : return;
9432 : }
9433 :
9434 0 : NS_ASSERTION(gfxSkipCharsIterator(iter).ConvertOriginalToSkipped(offset + length)
9435 : <= mTextRun->GetLength(),
9436 : "Text run does not map enough text for our reflow");
9437 :
9438 : /////////////////////////////////////////////////////////////////////
9439 : // See how much text should belong to this text frame, and measure it
9440 : /////////////////////////////////////////////////////////////////////
9441 :
9442 0 : iter.SetOriginalOffset(offset);
9443 0 : nscoord xOffsetForTabs = (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_HAS_TAB) ?
9444 0 : (aLineLayout.GetCurrentFrameInlineDistanceFromBlock() -
9445 0 : lineContainer->GetUsedBorderAndPadding().left)
9446 0 : : -1;
9447 : PropertyProvider provider(mTextRun, textStyle, frag, this, iter, length,
9448 0 : lineContainer, xOffsetForTabs, nsTextFrame::eInflated);
9449 :
9450 0 : uint32_t transformedOffset = provider.GetStart().GetSkippedOffset();
9451 :
9452 : // The metrics for the text go in here
9453 0 : gfxTextRun::Metrics textMetrics;
9454 : gfxFont::BoundingBoxType boundingBoxType =
9455 0 : IsFloatingFirstLetterChild() || IsInitialLetterChild()
9456 0 : ? gfxFont::TIGHT_HINTED_OUTLINE_EXTENTS
9457 0 : : gfxFont::LOOSE_INK_EXTENTS;
9458 :
9459 5 : int32_t limitLength = length;
9460 10 : int32_t forceBreak = aLineLayout.GetForcedBreakPosition(this);
9461 0 : bool forceBreakAfter = false;
9462 0 : if (forceBreak >= length) {
9463 0 : forceBreakAfter = forceBreak == length;
9464 : // The break is not within the text considered for this textframe.
9465 0 : forceBreak = -1;
9466 : }
9467 0 : if (forceBreak >= 0) {
9468 0 : limitLength = forceBreak;
9469 : }
9470 : // This is the heart of text reflow right here! We don't know where
9471 : // to break, so we need to see how much text fits in the available width.
9472 : uint32_t transformedLength;
9473 10 : if (offset + limitLength >= int32_t(frag->GetLength())) {
9474 5 : NS_ASSERTION(offset + limitLength == int32_t(frag->GetLength()),
9475 : "Content offset/length out of bounds");
9476 0 : NS_ASSERTION(flowEndInTextRun >= transformedOffset,
9477 : "Negative flow length?");
9478 0 : transformedLength = flowEndInTextRun - transformedOffset;
9479 : } else {
9480 : // we're not looking at all the content, so we need to compute the
9481 : // length of the transformed substring we're looking at
9482 0 : gfxSkipCharsIterator iter(provider.GetStart());
9483 0 : iter.SetOriginalOffset(offset + limitLength);
9484 0 : transformedLength = iter.GetSkippedOffset() - transformedOffset;
9485 : }
9486 0 : uint32_t transformedLastBreak = 0;
9487 : bool usedHyphenation;
9488 0 : gfxFloat trimmedWidth = 0;
9489 5 : gfxFloat availWidth = aAvailableWidth;
9490 0 : if (Style()->IsTextCombined()) {
9491 : // If text-combine-upright is 'all', we would compress whatever long
9492 : // text into ~1em width, so there is no limited on the avail width.
9493 0 : availWidth = std::numeric_limits<gfxFloat>::infinity();
9494 : }
9495 0 : bool canTrimTrailingWhitespace = !textStyle->WhiteSpaceIsSignificant() ||
9496 11 : (GetStateBits() & TEXT_IS_IN_TOKEN_MATHML);
9497 : // allow whitespace to overflow the container
9498 0 : bool whitespaceCanHang = textStyle->WhiteSpaceCanWrapStyle() &&
9499 5 : textStyle->WhiteSpaceIsSignificant();
9500 0 : gfxBreakPriority breakPriority = aLineLayout.LastOptionalBreakPriority();
9501 0 : gfxTextRun::SuppressBreak suppressBreak = gfxTextRun::eNoSuppressBreak;
9502 0 : bool shouldSuppressLineBreak = ShouldSuppressLineBreak();
9503 0 : if (shouldSuppressLineBreak) {
9504 : suppressBreak = gfxTextRun::eSuppressAllBreaks;
9505 0 : } else if (!aLineLayout.LineIsBreakable()) {
9506 5 : suppressBreak = gfxTextRun::eSuppressInitialBreak;
9507 : }
9508 : uint32_t transformedCharsFit =
9509 10 : mTextRun->BreakAndMeasureText(transformedOffset, transformedLength,
9510 10 : (GetStateBits() & TEXT_START_OF_LINE) != 0,
9511 : availWidth,
9512 : &provider, suppressBreak,
9513 : canTrimTrailingWhitespace ? &trimmedWidth : nullptr,
9514 : whitespaceCanHang,
9515 : &textMetrics, boundingBoxType,
9516 : aDrawTarget,
9517 : &usedHyphenation, &transformedLastBreak,
9518 10 : textStyle->WordCanWrap(this), &breakPriority);
9519 5 : if (!length && !textMetrics.mAscent && !textMetrics.mDescent) {
9520 : // If we're measuring a zero-length piece of text, update
9521 : // the height manually.
9522 0 : nsFontMetrics* fm = provider.GetFontMetrics();
9523 0 : if (fm) {
9524 0 : textMetrics.mAscent = gfxFloat(fm->MaxAscent());
9525 0 : textMetrics.mDescent = gfxFloat(fm->MaxDescent());
9526 : }
9527 : }
9528 5 : if (GetWritingMode().IsLineInverted()) {
9529 0 : Swap(textMetrics.mAscent, textMetrics.mDescent);
9530 0 : textMetrics.mBoundingBox.y = -textMetrics.mBoundingBox.YMost();
9531 : }
9532 : // The "end" iterator points to the first character after the string mapped
9533 : // by this frame. Basically, its original-string offset is offset+charsFit
9534 : // after we've computed charsFit.
9535 5 : gfxSkipCharsIterator end(provider.GetEndHint());
9536 5 : end.SetSkippedOffset(transformedOffset + transformedCharsFit);
9537 0 : int32_t charsFit = end.GetOriginalOffset() - offset;
9538 0 : if (offset + charsFit == newLineOffset) {
9539 : // We broke before a trailing preformatted '\n'. The newline should
9540 : // be assigned to this frame. Note that newLineOffset will be -1 if
9541 : // there was no preformatted newline, so we wouldn't get here in that
9542 : // case.
9543 0 : ++charsFit;
9544 : }
9545 : // That might have taken us beyond our assigned content range (because
9546 : // we might have advanced over some skipped chars that extend outside
9547 : // this frame), so get back in.
9548 5 : int32_t lastBreak = -1;
9549 5 : if (charsFit >= limitLength) {
9550 0 : charsFit = limitLength;
9551 0 : if (transformedLastBreak != UINT32_MAX) {
9552 : // lastBreak is needed.
9553 : // This may set lastBreak greater than 'length', but that's OK
9554 0 : lastBreak = end.ConvertSkippedToOriginal(transformedOffset + transformedLastBreak);
9555 : }
9556 0 : end.SetOriginalOffset(offset + charsFit);
9557 : // If we were forced to fit, and the break position is after a soft hyphen,
9558 : // note that this is a hyphenation break.
9559 5 : if ((forceBreak >= 0 || forceBreakAfter) &&
9560 0 : HasSoftHyphenBefore(frag, mTextRun, offset, end)) {
9561 0 : usedHyphenation = true;
9562 : }
9563 : }
9564 5 : if (usedHyphenation) {
9565 : // Fix up metrics to include hyphen
9566 : AddHyphenToMetrics(this, mTextRun, &textMetrics, boundingBoxType,
9567 0 : aDrawTarget);
9568 0 : AddStateBits(TEXT_HYPHEN_BREAK | TEXT_HAS_NONCOLLAPSED_CHARACTERS);
9569 : }
9570 0 : if (textMetrics.mBoundingBox.IsEmpty()) {
9571 0 : AddStateBits(TEXT_NO_RENDERED_GLYPHS);
9572 : }
9573 :
9574 5 : gfxFloat trimmableWidth = 0;
9575 5 : bool brokeText = forceBreak >= 0 || transformedCharsFit < transformedLength;
9576 0 : if (canTrimTrailingWhitespace) {
9577 : // Optimization: if we trimmed trailing whitespace, and we can be sure
9578 : // this frame will be at the end of the line, then leave it trimmed off.
9579 : // Otherwise we have to undo the trimming, in case we're not at the end of
9580 : // the line. (If we actually do end up at the end of the line, we'll have
9581 : // to trim it off again in TrimTrailingWhiteSpace, and we'd like to avoid
9582 : // having to re-do it.)
9583 4 : if (brokeText ||
9584 4 : (GetStateBits() & TEXT_IS_IN_TOKEN_MATHML)) {
9585 : // We're definitely going to break so our trailing whitespace should
9586 : // definitely be trimmed. Record that we've already done it.
9587 0 : AddStateBits(TEXT_TRIMMED_TRAILING_WHITESPACE);
9588 4 : } else if (!(GetStateBits() & TEXT_IS_IN_TOKEN_MATHML)) {
9589 : // We might not be at the end of the line. (Note that even if this frame
9590 : // ends in breakable whitespace, it might not be at the end of the line
9591 : // because it might be followed by breakable, but preformatted, whitespace.)
9592 : // Undo the trimming.
9593 2 : textMetrics.mAdvanceWidth += trimmedWidth;
9594 2 : trimmableWidth = trimmedWidth;
9595 0 : if (mTextRun->IsRightToLeft()) {
9596 : // Space comes before text, so the bounding box is moved to the
9597 : // right by trimmdWidth
9598 0 : textMetrics.mBoundingBox.MoveBy(gfxPoint(trimmedWidth, 0));
9599 : }
9600 : }
9601 : }
9602 :
9603 5 : if (!brokeText && lastBreak >= 0) {
9604 : // Since everything fit and no break was forced,
9605 : // record the last break opportunity
9606 0 : NS_ASSERTION(textMetrics.mAdvanceWidth - trimmableWidth <= availWidth,
9607 : "If the text doesn't fit, and we have a break opportunity, why didn't MeasureText use it?");
9608 0 : MOZ_ASSERT(lastBreak >= offset, "Strange break position");
9609 0 : aLineLayout.NotifyOptionalBreakPosition(this, lastBreak - offset,
9610 0 : true, breakPriority);
9611 : }
9612 :
9613 5 : int32_t contentLength = offset + charsFit - GetContentOffset();
9614 :
9615 : /////////////////////////////////////////////////////////////////////
9616 : // Compute output metrics
9617 : /////////////////////////////////////////////////////////////////////
9618 :
9619 : // first-letter frames should use the tight bounding box metrics for ascent/descent
9620 : // for good drop-cap effects
9621 10 : if (GetStateBits() & TEXT_FIRST_LETTER) {
9622 0 : textMetrics.mAscent = std::max(gfxFloat(0.0), -textMetrics.mBoundingBox.Y());
9623 0 : textMetrics.mDescent = std::max(gfxFloat(0.0), textMetrics.mBoundingBox.YMost());
9624 : }
9625 :
9626 : // Setup metrics for caller
9627 : // Disallow negative widths
9628 5 : WritingMode wm = GetWritingMode();
9629 5 : LogicalSize finalSize(wm);
9630 0 : finalSize.ISize(wm) = NSToCoordCeil(std::max(gfxFloat(0.0),
9631 0 : textMetrics.mAdvanceWidth));
9632 :
9633 0 : if (transformedCharsFit == 0 && !usedHyphenation) {
9634 0 : aMetrics.SetBlockStartAscent(0);
9635 0 : finalSize.BSize(wm) = 0;
9636 0 : } else if (boundingBoxType != gfxFont::LOOSE_INK_EXTENTS) {
9637 : // Use actual text metrics for floating first letter frame.
9638 0 : aMetrics.SetBlockStartAscent(NSToCoordCeil(textMetrics.mAscent));
9639 0 : finalSize.BSize(wm) = aMetrics.BlockStartAscent() +
9640 0 : NSToCoordCeil(textMetrics.mDescent);
9641 : } else {
9642 : // Otherwise, ascent should contain the overline drawable area.
9643 : // And also descent should contain the underline drawable area.
9644 : // nsFontMetrics::GetMaxAscent/GetMaxDescent contains them.
9645 5 : nsFontMetrics* fm = provider.GetFontMetrics();
9646 : nscoord fontAscent =
9647 0 : wm.IsLineInverted() ? fm->MaxDescent() : fm->MaxAscent();
9648 : nscoord fontDescent =
9649 0 : wm.IsLineInverted() ? fm->MaxAscent() : fm->MaxDescent();
9650 15 : aMetrics.SetBlockStartAscent(std::max(NSToCoordCeil(textMetrics.mAscent), fontAscent));
9651 0 : nscoord descent = std::max(NSToCoordCeil(textMetrics.mDescent), fontDescent);
9652 0 : finalSize.BSize(wm) = aMetrics.BlockStartAscent() + descent;
9653 : }
9654 0 : if (Style()->IsTextCombined()) {
9655 0 : nsFontMetrics* fm = provider.GetFontMetrics();
9656 0 : gfxFloat width = finalSize.ISize(wm);
9657 0 : gfxFloat em = fm->EmHeight();
9658 : // Compress the characters in horizontal axis if necessary.
9659 0 : if (width <= em) {
9660 0 : RemoveProperty(TextCombineScaleFactorProperty());
9661 : } else {
9662 0 : SetProperty(TextCombineScaleFactorProperty(), em / width);
9663 0 : finalSize.ISize(wm) = em;
9664 : }
9665 : // Make the characters be in an 1em square.
9666 0 : if (finalSize.BSize(wm) != em) {
9667 0 : aMetrics.SetBlockStartAscent(aMetrics.BlockStartAscent() +
9668 0 : (em - finalSize.BSize(wm)) / 2);
9669 0 : finalSize.BSize(wm) = em;
9670 : }
9671 : }
9672 5 : aMetrics.SetSize(wm, finalSize);
9673 :
9674 0 : NS_ASSERTION(aMetrics.BlockStartAscent() >= 0,
9675 : "Negative ascent???");
9676 0 : NS_ASSERTION((Style()->IsTextCombined()
9677 : ? aMetrics.ISize(aMetrics.GetWritingMode())
9678 : : aMetrics.BSize(aMetrics.GetWritingMode())) -
9679 : aMetrics.BlockStartAscent() >= 0,
9680 : "Negative descent???");
9681 :
9682 5 : mAscent = aMetrics.BlockStartAscent();
9683 :
9684 : // Handle text that runs outside its normal bounds.
9685 10 : nsRect boundingBox = RoundOut(textMetrics.mBoundingBox);
9686 10 : if (mTextRun->IsVertical()) {
9687 : // Swap line-relative textMetrics dimensions to physical coordinates.
9688 0 : Swap(boundingBox.x, boundingBox.y);
9689 0 : Swap(boundingBox.width, boundingBox.height);
9690 0 : if (GetWritingMode().IsVerticalRL()) {
9691 0 : boundingBox.x = -boundingBox.XMost();
9692 0 : boundingBox.x += aMetrics.Width() - mAscent;
9693 : } else {
9694 0 : boundingBox.x += mAscent;
9695 : }
9696 : } else {
9697 5 : boundingBox.y += mAscent;
9698 : }
9699 0 : aMetrics.SetOverflowAreasToDesiredBounds();
9700 10 : aMetrics.VisualOverflow().UnionRect(aMetrics.VisualOverflow(), boundingBox);
9701 :
9702 : // When we have text decorations, we don't need to compute their overflow now
9703 : // because we're guaranteed to do it later
9704 : // (see nsLineLayout::RelativePositionFrames)
9705 5 : UnionAdditionalOverflow(presContext, aLineLayout.LineContainerRI()->mFrame,
9706 10 : provider, &aMetrics.VisualOverflow(), false);
9707 :
9708 : /////////////////////////////////////////////////////////////////////
9709 : // Clean up, update state
9710 : /////////////////////////////////////////////////////////////////////
9711 :
9712 : // If all our characters are discarded or collapsed, then trimmable width
9713 : // from the last textframe should be preserved. Otherwise the trimmable width
9714 : // from this textframe overrides. (Currently in CSS trimmable width can be
9715 : // at most one space so there's no way for trimmable width from a previous
9716 : // frame to accumulate with trimmable width from this frame.)
9717 5 : if (transformedCharsFit > 0) {
9718 10 : aLineLayout.SetTrimmableISize(NSToCoordFloor(trimmableWidth));
9719 0 : AddStateBits(TEXT_HAS_NONCOLLAPSED_CHARACTERS);
9720 : }
9721 0 : bool breakAfter = forceBreakAfter;
9722 5 : if (!shouldSuppressLineBreak) {
9723 0 : if (charsFit > 0 && charsFit == length &&
9724 0 : textStyle->mHyphens != StyleHyphens::None &&
9725 0 : HasSoftHyphenBefore(frag, mTextRun, offset, end)) {
9726 : bool fits =
9727 0 : textMetrics.mAdvanceWidth + provider.GetHyphenWidth() <= availWidth;
9728 : // Record a potential break after final soft hyphen
9729 0 : aLineLayout.NotifyOptionalBreakPosition(this, length, fits,
9730 0 : gfxBreakPriority::eNormalBreak);
9731 : }
9732 : // length == 0 means either the text is empty or it's all collapsed away
9733 5 : bool emptyTextAtStartOfLine = atStartOfLine && length == 0;
9734 20 : if (!breakAfter && charsFit == length && !emptyTextAtStartOfLine &&
9735 0 : transformedOffset + transformedLength == mTextRun->GetLength() &&
9736 0 : (mTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_HAS_TRAILING_BREAK)) {
9737 : // We placed all the text in the textrun and we have a break opportunity
9738 : // at the end of the textrun. We need to record it because the following
9739 : // content may not care about nsLineBreaker.
9740 :
9741 : // Note that because we didn't break, we can be sure that (thanks to the
9742 : // code up above) textMetrics.mAdvanceWidth includes the width of any
9743 : // trailing whitespace. So we need to subtract trimmableWidth here
9744 : // because if we did break at this point, that much width would be
9745 : // trimmed.
9746 0 : if (textMetrics.mAdvanceWidth - trimmableWidth > availWidth) {
9747 : breakAfter = true;
9748 : } else {
9749 : aLineLayout.NotifyOptionalBreakPosition(this, length, true,
9750 0 : gfxBreakPriority::eNormalBreak);
9751 : }
9752 : }
9753 : }
9754 :
9755 : // Compute reflow status
9756 5 : if (contentLength != maxContentLength) {
9757 : aStatus.SetIncomplete();
9758 : }
9759 :
9760 5 : if (charsFit == 0 && length > 0 && !usedHyphenation) {
9761 : // Couldn't place any text
9762 0 : aStatus.SetInlineLineBreakBeforeAndReset();
9763 5 : } else if (contentLength > 0 && mContentOffset + contentLength - 1 == newLineOffset) {
9764 : // Ends in \n
9765 0 : aStatus.SetInlineLineBreakAfter();
9766 : aLineLayout.SetLineEndsInBR(true);
9767 0 : } else if (breakAfter) {
9768 0 : aStatus.SetInlineLineBreakAfter();
9769 : }
9770 0 : if (completedFirstLetter) {
9771 0 : aLineLayout.SetFirstLetterStyleOK(false);
9772 : aStatus.SetFirstLetterComplete();
9773 : }
9774 :
9775 : // Updated the cached NewlineProperty, or delete it.
9776 5 : if (contentLength < maxContentLength &&
9777 5 : textStyle->NewlineIsSignificant(this) &&
9778 0 : (contentNewLineOffset < 0 ||
9779 0 : mContentOffset + contentLength <= contentNewLineOffset)) {
9780 0 : if (!cachedNewlineOffset) {
9781 0 : cachedNewlineOffset = new NewlineProperty;
9782 0 : if (NS_FAILED(mContent->SetProperty(nsGkAtoms::newline, cachedNewlineOffset,
9783 : nsINode::DeleteProperty<NewlineProperty>))) {
9784 0 : delete cachedNewlineOffset;
9785 0 : cachedNewlineOffset = nullptr;
9786 : }
9787 0 : mContent->SetFlags(NS_HAS_NEWLINE_PROPERTY);
9788 : }
9789 0 : if (cachedNewlineOffset) {
9790 0 : cachedNewlineOffset->mStartOffset = offset;
9791 0 : cachedNewlineOffset->mNewlineOffset = contentNewLineOffset;
9792 : }
9793 0 : } else if (cachedNewlineOffset) {
9794 0 : mContent->DeleteProperty(nsGkAtoms::newline);
9795 0 : mContent->UnsetFlags(NS_HAS_NEWLINE_PROPERTY);
9796 : }
9797 :
9798 : // Compute space and letter counts for justification, if required
9799 12 : if (!textStyle->WhiteSpaceIsSignificant() &&
9800 4 : (lineContainer->StyleText()->mTextAlign == NS_STYLE_TEXT_ALIGN_JUSTIFY ||
9801 0 : lineContainer->StyleText()->mTextAlignLast == NS_STYLE_TEXT_ALIGN_JUSTIFY ||
9802 0 : shouldSuppressLineBreak) &&
9803 0 : !nsSVGUtils::IsInSVGTextSubtree(lineContainer)) {
9804 0 : AddStateBits(TEXT_JUSTIFICATION_ENABLED);
9805 0 : Range range(uint32_t(offset), uint32_t(offset + charsFit));
9806 0 : aLineLayout.SetJustificationInfo(provider.ComputeJustification(range));
9807 : }
9808 :
9809 5 : SetLength(contentLength, &aLineLayout, ALLOW_FRAME_CREATION_AND_DESTRUCTION);
9810 :
9811 0 : InvalidateFrame();
9812 :
9813 : #ifdef NOISY_REFLOW
9814 : ListTag(stdout);
9815 : printf(": desiredSize=%d,%d(b=%d) status=%x\n",
9816 : aMetrics.Width(), aMetrics.Height(), aMetrics.BlockStartAscent(),
9817 : aStatus);
9818 : #endif
9819 : }
9820 :
9821 : /* virtual */ bool
9822 11 : nsTextFrame::CanContinueTextRun() const
9823 : {
9824 : // We can continue a text run through a text frame
9825 11 : return true;
9826 : }
9827 :
9828 : nsTextFrame::TrimOutput
9829 11 : nsTextFrame::TrimTrailingWhiteSpace(DrawTarget* aDrawTarget)
9830 : {
9831 0 : MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_DIRTY | NS_FRAME_FIRST_REFLOW),
9832 : "frame should have been reflowed");
9833 :
9834 : TrimOutput result;
9835 11 : result.mChanged = false;
9836 11 : result.mDeltaWidth = 0;
9837 :
9838 0 : AddStateBits(TEXT_END_OF_LINE);
9839 :
9840 0 : if (!GetTextRun(nsTextFrame::eInflated)) {
9841 : // If reflow didn't create a textrun, there must have been no content once
9842 : // leading whitespace was trimmed, so nothing more to do here.
9843 2 : return result;
9844 : }
9845 :
9846 9 : int32_t contentLength = GetContentLength();
9847 9 : if (!contentLength)
9848 0 : return result;
9849 :
9850 : gfxSkipCharsIterator start =
9851 5 : EnsureTextRun(nsTextFrame::eInflated, aDrawTarget);
9852 10 : NS_ENSURE_TRUE(mTextRun, result);
9853 :
9854 0 : uint32_t trimmedStart = start.GetSkippedOffset();
9855 :
9856 0 : const nsTextFragment* frag = mContent->GetText();
9857 5 : TrimmedOffsets trimmed = GetTrimmedOffsets(frag, true);
9858 0 : gfxSkipCharsIterator trimmedEndIter = start;
9859 0 : const nsStyleText* textStyle = StyleText();
9860 0 : gfxFloat delta = 0;
9861 0 : uint32_t trimmedEnd = trimmedEndIter.ConvertOriginalToSkipped(trimmed.GetEnd());
9862 :
9863 0 : if (!(GetStateBits() & TEXT_TRIMMED_TRAILING_WHITESPACE) &&
9864 5 : trimmed.GetEnd() < GetContentEnd()) {
9865 0 : gfxSkipCharsIterator end = trimmedEndIter;
9866 0 : uint32_t endOffset = end.ConvertOriginalToSkipped(GetContentOffset() + contentLength);
9867 0 : if (trimmedEnd < endOffset) {
9868 : // We can't be dealing with tabs here ... they wouldn't be trimmed. So it's
9869 : // OK to pass null for the line container.
9870 : PropertyProvider provider(mTextRun, textStyle, frag, this, start, contentLength,
9871 0 : nullptr, 0, nsTextFrame::eInflated);
9872 : delta = mTextRun->
9873 0 : GetAdvanceWidth(Range(trimmedEnd, endOffset), &provider);
9874 0 : result.mChanged = true;
9875 : }
9876 : }
9877 :
9878 : gfxFloat advanceDelta;
9879 20 : mTextRun->SetLineBreaks(Range(trimmedStart, trimmedEnd),
9880 10 : (GetStateBits() & TEXT_START_OF_LINE) != 0, true,
9881 0 : &advanceDelta);
9882 0 : if (advanceDelta != 0) {
9883 0 : result.mChanged = true;
9884 : }
9885 :
9886 : // aDeltaWidth is *subtracted* from our width.
9887 : // If advanceDelta is positive then setting the line break made us longer,
9888 : // so aDeltaWidth could go negative.
9889 5 : result.mDeltaWidth = NSToCoordFloor(delta - advanceDelta);
9890 : // If aDeltaWidth goes negative, that means this frame might not actually fit
9891 : // anymore!!! We need higher level line layout to recover somehow.
9892 : // If it's because the frame has a soft hyphen that is now being displayed,
9893 : // this should actually be OK, because our reflow recorded the break
9894 : // opportunity that allowed the soft hyphen to be used, and we wouldn't
9895 : // have recorded the opportunity unless the hyphen fit (or was the first
9896 : // opportunity on the line).
9897 : // Otherwise this can/ really only happen when we have glyphs with special
9898 : // shapes at the end of lines, I think. Breaking inside a kerning pair won't
9899 : // do it because that would mean we broke inside this textrun, and
9900 : // BreakAndMeasureText should make sure the resulting shaped substring fits.
9901 : // Maybe if we passed a maxTextLength? But that only happens at direction
9902 : // changes (so we wouldn't kern across the boundary) or for first-letter
9903 : // (which always fits because it starts the line!).
9904 5 : NS_WARNING_ASSERTION(result.mDeltaWidth >= 0,
9905 : "Negative deltawidth, something odd is happening");
9906 :
9907 : #ifdef NOISY_TRIM
9908 : ListTag(stdout);
9909 : printf(": trim => %d\n", result.mDeltaWidth);
9910 : #endif
9911 5 : return result;
9912 : }
9913 :
9914 : nsOverflowAreas
9915 0 : nsTextFrame::RecomputeOverflow(nsIFrame* aBlockFrame)
9916 : {
9917 0 : nsRect bounds(nsPoint(0, 0), GetSize());
9918 0 : nsOverflowAreas result(bounds, bounds);
9919 :
9920 0 : gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
9921 0 : if (!mTextRun)
9922 : return result;
9923 :
9924 0 : PropertyProvider provider(this, iter, nsTextFrame::eInflated);
9925 : // Don't trim trailing space, in case we need to paint it as selected.
9926 0 : provider.InitializeForDisplay(false);
9927 :
9928 : gfxTextRun::Metrics textMetrics =
9929 : mTextRun->MeasureText(ComputeTransformedRange(provider),
9930 : gfxFont::LOOSE_INK_EXTENTS, nullptr,
9931 0 : &provider);
9932 0 : if (GetWritingMode().IsLineInverted()) {
9933 0 : textMetrics.mBoundingBox.y = -textMetrics.mBoundingBox.YMost();
9934 : }
9935 0 : nsRect boundingBox = RoundOut(textMetrics.mBoundingBox);
9936 0 : boundingBox += nsPoint(0, mAscent);
9937 0 : if (mTextRun->IsVertical()) {
9938 : // Swap line-relative textMetrics dimensions to physical coordinates.
9939 0 : Swap(boundingBox.x, boundingBox.y);
9940 : Swap(boundingBox.width, boundingBox.height);
9941 : }
9942 0 : nsRect &vis = result.VisualOverflow();
9943 0 : vis.UnionRect(vis, boundingBox);
9944 0 : UnionAdditionalOverflow(PresContext(), aBlockFrame, provider, &vis, true);
9945 : return result;
9946 : }
9947 :
9948 0 : static void TransformChars(nsTextFrame* aFrame, const nsStyleText* aStyle,
9949 : const gfxTextRun* aTextRun, uint32_t aSkippedOffset,
9950 : const nsTextFragment* aFrag, int32_t aFragOffset,
9951 : int32_t aFragLen, nsAString& aOut)
9952 : {
9953 0 : nsAutoString fragString;
9954 : char16_t* out;
9955 0 : if (aStyle->mTextTransform == NS_STYLE_TEXT_TRANSFORM_NONE) {
9956 : // No text-transform, so we can copy directly to the output string.
9957 0 : aOut.SetLength(aOut.Length() + aFragLen);
9958 0 : out = aOut.EndWriting() - aFragLen;
9959 : } else {
9960 : // Use a temporary string as source for the transform.
9961 0 : fragString.SetLength(aFragLen);
9962 0 : out = fragString.BeginWriting();
9963 : }
9964 :
9965 : // Copy the text, with \n and \t replaced by <space> if appropriate.
9966 0 : for (int32_t i = 0; i < aFragLen; ++i) {
9967 0 : char16_t ch = aFrag->CharAt(aFragOffset + i);
9968 0 : if ((ch == '\n' && !aStyle->NewlineIsSignificant(aFrame)) ||
9969 0 : (ch == '\t' && !aStyle->TabIsSignificant())) {
9970 0 : ch = ' ';
9971 : }
9972 0 : out[i] = ch;
9973 : }
9974 :
9975 0 : if (aStyle->mTextTransform != NS_STYLE_TEXT_TRANSFORM_NONE) {
9976 0 : MOZ_ASSERT(aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_IS_TRANSFORMED);
9977 0 : if (aTextRun->GetFlags2() & nsTextFrameUtils::Flags::TEXT_IS_TRANSFORMED) {
9978 : // Apply text-transform according to style in the transformed run.
9979 : auto transformedTextRun =
9980 0 : static_cast<const nsTransformedTextRun*>(aTextRun);
9981 0 : nsAutoString convertedString;
9982 0 : AutoTArray<bool,50> charsToMergeArray;
9983 0 : AutoTArray<bool,50> deletedCharsArray;
9984 : nsCaseTransformTextRunFactory::TransformString(fragString,
9985 : convertedString,
9986 : false, nullptr,
9987 : charsToMergeArray,
9988 : deletedCharsArray,
9989 : transformedTextRun,
9990 0 : aSkippedOffset);
9991 0 : aOut.Append(convertedString);
9992 : } else {
9993 : // Should not happen (see assertion above), but as a fallback...
9994 0 : aOut.Append(fragString);
9995 : }
9996 : }
9997 0 : }
9998 :
9999 : static bool
10000 0 : LineEndsInHardLineBreak(nsTextFrame* aFrame, nsBlockFrame* aLineContainer)
10001 : {
10002 : bool foundValidLine;
10003 0 : nsBlockInFlowLineIterator iter(aLineContainer, aFrame, &foundValidLine);
10004 0 : if (!foundValidLine) {
10005 0 : NS_ERROR("Invalid line!");
10006 0 : return true;
10007 : }
10008 0 : return !iter.GetLine()->IsLineWrapped();
10009 : }
10010 :
10011 : nsIFrame::RenderedText
10012 0 : nsTextFrame::GetRenderedText(uint32_t aStartOffset,
10013 : uint32_t aEndOffset,
10014 : TextOffsetType aOffsetType,
10015 : TrailingWhitespace aTrimTrailingWhitespace)
10016 : {
10017 0 : MOZ_ASSERT(aStartOffset <= aEndOffset, "bogus offsets");
10018 0 : MOZ_ASSERT(!GetPrevContinuation() ||
10019 : (aOffsetType == TextOffsetType::OFFSETS_IN_CONTENT_TEXT &&
10020 : aStartOffset >= (uint32_t)GetContentOffset() &&
10021 : aEndOffset <= (uint32_t)GetContentEnd()),
10022 : "Must be called on first-in-flow, or content offsets must be "
10023 : "given and be within this frame.");
10024 :
10025 : // The handling of offsets could be more efficient...
10026 0 : RenderedText result;
10027 0 : nsBlockFrame* lineContainer = nullptr;
10028 : nsTextFrame* textFrame;
10029 0 : const nsTextFragment* textFrag = mContent->GetText();
10030 0 : uint32_t offsetInRenderedString = 0;
10031 0 : bool haveOffsets = false;
10032 :
10033 0 : Maybe<nsBlockFrame::AutoLineCursorSetup> autoLineCursor;
10034 0 : for (textFrame = this; textFrame;
10035 : textFrame = textFrame->GetNextContinuation()) {
10036 0 : if (textFrame->GetStateBits() & NS_FRAME_IS_DIRTY) {
10037 : // We don't trust dirty frames, especially when computing rendered text.
10038 : break;
10039 : }
10040 :
10041 : // Ensure the text run and grab the gfxSkipCharsIterator for it
10042 : gfxSkipCharsIterator iter =
10043 0 : textFrame->EnsureTextRun(nsTextFrame::eInflated);
10044 0 : if (!textFrame->mTextRun) {
10045 : break;
10046 : }
10047 0 : gfxSkipCharsIterator tmpIter = iter;
10048 :
10049 : // Whether we need to trim whitespaces after the text frame.
10050 : bool trimAfter;
10051 0 : if (!textFrame->IsAtEndOfLine() ||
10052 : aTrimTrailingWhitespace !=
10053 : TrailingWhitespace::TRIM_TRAILING_WHITESPACE) {
10054 : trimAfter = false;
10055 0 : } else if (nsBlockFrame* thisLc =
10056 0 : do_QueryFrame(FindLineContainer(textFrame))) {
10057 0 : if (thisLc != lineContainer) {
10058 : // Setup line cursor when needed.
10059 0 : lineContainer = thisLc;
10060 0 : autoLineCursor.reset();
10061 0 : autoLineCursor.emplace(lineContainer);
10062 : }
10063 0 : trimAfter = LineEndsInHardLineBreak(textFrame, lineContainer);
10064 : } else {
10065 : // Weird situation where we have a line layout without a block.
10066 : // No soft breaks occur in this situation.
10067 : trimAfter = true;
10068 : }
10069 :
10070 : // Skip to the start of the text run, past ignored chars at start of line
10071 : TrimmedOffsets trimmedOffsets =
10072 0 : textFrame->GetTrimmedOffsets(textFrag, trimAfter);
10073 : bool trimmedSignificantNewline =
10074 0 : trimmedOffsets.GetEnd() < GetContentEnd() &&
10075 0 : HasSignificantTerminalNewline();
10076 : uint32_t skippedToRenderedStringOffset = offsetInRenderedString -
10077 0 : tmpIter.ConvertOriginalToSkipped(trimmedOffsets.mStart);
10078 : uint32_t nextOffsetInRenderedString =
10079 0 : tmpIter.ConvertOriginalToSkipped(trimmedOffsets.GetEnd()) +
10080 0 : (trimmedSignificantNewline ? 1 : 0) + skippedToRenderedStringOffset;
10081 :
10082 0 : if (aOffsetType == TextOffsetType::OFFSETS_IN_RENDERED_TEXT) {
10083 0 : if (nextOffsetInRenderedString <= aStartOffset) {
10084 : offsetInRenderedString = nextOffsetInRenderedString;
10085 0 : continue;
10086 : }
10087 0 : if (!haveOffsets) {
10088 0 : result.mOffsetWithinNodeText =
10089 0 : tmpIter.ConvertSkippedToOriginal(aStartOffset - skippedToRenderedStringOffset);
10090 0 : result.mOffsetWithinNodeRenderedText = aStartOffset;
10091 0 : haveOffsets = true;
10092 : }
10093 0 : if (offsetInRenderedString >= aEndOffset) {
10094 : break;
10095 : }
10096 : } else {
10097 0 : if (uint32_t(textFrame->GetContentEnd()) <= aStartOffset) {
10098 : offsetInRenderedString = nextOffsetInRenderedString;
10099 : continue;
10100 : }
10101 0 : if (!haveOffsets) {
10102 0 : result.mOffsetWithinNodeText = aStartOffset;
10103 : // Skip trimmed space when computed the rendered text offset.
10104 0 : int32_t clamped = std::max<int32_t>(aStartOffset, trimmedOffsets.mStart);
10105 0 : result.mOffsetWithinNodeRenderedText =
10106 0 : tmpIter.ConvertOriginalToSkipped(clamped) + skippedToRenderedStringOffset;
10107 0 : MOZ_ASSERT(result.mOffsetWithinNodeRenderedText >= offsetInRenderedString &&
10108 : result.mOffsetWithinNodeRenderedText <= INT32_MAX,
10109 : "Bad offset within rendered text");
10110 : haveOffsets = true;
10111 : }
10112 0 : if (uint32_t(textFrame->mContentOffset) >= aEndOffset) {
10113 : break;
10114 : }
10115 : }
10116 :
10117 : int32_t startOffset;
10118 : int32_t endOffset;
10119 0 : if (aOffsetType == TextOffsetType::OFFSETS_IN_RENDERED_TEXT) {
10120 : startOffset =
10121 0 : tmpIter.ConvertSkippedToOriginal(aStartOffset - skippedToRenderedStringOffset);
10122 : endOffset =
10123 0 : tmpIter.ConvertSkippedToOriginal(aEndOffset - skippedToRenderedStringOffset);
10124 : } else {
10125 0 : startOffset = aStartOffset;
10126 0 : endOffset = std::min<uint32_t>(INT32_MAX, aEndOffset);
10127 : }
10128 :
10129 : // If startOffset and/or endOffset are inside of trimmedOffsets' range,
10130 : // then clamp the edges of trimmedOffsets accordingly.
10131 0 : int32_t origTrimmedOffsetsEnd = trimmedOffsets.GetEnd();
10132 0 : trimmedOffsets.mStart = std::max<uint32_t>(trimmedOffsets.mStart,
10133 0 : startOffset);
10134 0 : trimmedOffsets.mLength = std::min<uint32_t>(origTrimmedOffsetsEnd,
10135 0 : endOffset) - trimmedOffsets.mStart;
10136 0 : if (trimmedOffsets.mLength <= 0) {
10137 : offsetInRenderedString = nextOffsetInRenderedString;
10138 : continue;
10139 : }
10140 :
10141 0 : const nsStyleText* textStyle = textFrame->StyleText();
10142 0 : iter.SetOriginalOffset(trimmedOffsets.mStart);
10143 0 : while (iter.GetOriginalOffset() < trimmedOffsets.GetEnd()) {
10144 : int32_t runLength;
10145 0 : bool isSkipped = iter.IsOriginalCharSkipped(&runLength);
10146 0 : runLength = std::min(runLength,
10147 0 : trimmedOffsets.GetEnd() - iter.GetOriginalOffset());
10148 0 : if (isSkipped) {
10149 0 : for (int32_t i = 0; i < runLength; ++i) {
10150 0 : char16_t ch = textFrag->CharAt(iter.GetOriginalOffset() + i);
10151 0 : if (ch == CH_SHY) {
10152 : // We should preserve soft hyphens. They can't be transformed.
10153 0 : result.mString.Append(ch);
10154 : }
10155 : }
10156 : } else {
10157 0 : TransformChars(textFrame, textStyle, textFrame->mTextRun,
10158 : iter.GetSkippedOffset(), textFrag,
10159 0 : iter.GetOriginalOffset(), runLength, result.mString);
10160 : }
10161 0 : iter.AdvanceOriginal(runLength);
10162 : }
10163 :
10164 0 : if (trimmedSignificantNewline && GetContentEnd() <= endOffset) {
10165 : // A significant newline was trimmed off (we must be
10166 : // white-space:pre-line). Put it back.
10167 0 : result.mString.Append('\n');
10168 : }
10169 0 : offsetInRenderedString = nextOffsetInRenderedString;
10170 : }
10171 :
10172 0 : if (!haveOffsets) {
10173 0 : result.mOffsetWithinNodeText = textFrag->GetLength();
10174 0 : result.mOffsetWithinNodeRenderedText = offsetInRenderedString;
10175 : }
10176 0 : return result;
10177 : }
10178 :
10179 : /* virtual */ bool
10180 24 : nsTextFrame::IsEmpty()
10181 : {
10182 0 : NS_ASSERTION(!(mState & TEXT_IS_ONLY_WHITESPACE) ||
10183 : !(mState & TEXT_ISNOT_ONLY_WHITESPACE),
10184 : "Invalid state");
10185 :
10186 : // XXXldb Should this check compatibility mode as well???
10187 24 : const nsStyleText* textStyle = StyleText();
10188 24 : if (textStyle->WhiteSpaceIsSignificant()) {
10189 : // XXX shouldn't we return true if the length is zero?
10190 : return false;
10191 : }
10192 :
10193 42 : if (mState & TEXT_ISNOT_ONLY_WHITESPACE) {
10194 : return false;
10195 : }
10196 :
10197 42 : if (mState & TEXT_IS_ONLY_WHITESPACE) {
10198 : return true;
10199 : }
10200 :
10201 : bool isEmpty =
10202 9 : IsAllWhitespace(mContent->GetText(),
10203 9 : textStyle->mWhiteSpace != mozilla::StyleWhiteSpace::PreLine);
10204 0 : AddStateBits(isEmpty ? TEXT_IS_ONLY_WHITESPACE : TEXT_ISNOT_ONLY_WHITESPACE);
10205 0 : return isEmpty;
10206 : }
10207 :
10208 : #ifdef DEBUG_FRAME_DUMP
10209 : // Translate the mapped content into a string that's printable
10210 : void
10211 0 : nsTextFrame::ToCString(nsCString& aBuf, int32_t* aTotalContentLength) const
10212 : {
10213 : // Get the frames text content
10214 0 : const nsTextFragment* frag = mContent->GetText();
10215 0 : if (!frag) {
10216 : return;
10217 : }
10218 :
10219 : // Compute the total length of the text content.
10220 0 : *aTotalContentLength = frag->GetLength();
10221 :
10222 0 : int32_t contentLength = GetContentLength();
10223 : // Set current fragment and current fragment offset
10224 0 : if (0 == contentLength) {
10225 : return;
10226 : }
10227 0 : int32_t fragOffset = GetContentOffset();
10228 0 : int32_t n = fragOffset + contentLength;
10229 0 : while (fragOffset < n) {
10230 0 : char16_t ch = frag->CharAt(fragOffset++);
10231 0 : if (ch == '\r') {
10232 0 : aBuf.AppendLiteral("\\r");
10233 0 : } else if (ch == '\n') {
10234 0 : aBuf.AppendLiteral("\\n");
10235 0 : } else if (ch == '\t') {
10236 0 : aBuf.AppendLiteral("\\t");
10237 0 : } else if ((ch < ' ') || (ch >= 127)) {
10238 0 : aBuf.Append(nsPrintfCString("\\u%04x", ch));
10239 : } else {
10240 0 : aBuf.Append(ch);
10241 : }
10242 : }
10243 : }
10244 :
10245 : nsresult
10246 0 : nsTextFrame::GetFrameName(nsAString& aResult) const
10247 : {
10248 0 : MakeFrameName(NS_LITERAL_STRING("Text"), aResult);
10249 : int32_t totalContentLength;
10250 0 : nsAutoCString tmp;
10251 0 : ToCString(tmp, &totalContentLength);
10252 0 : tmp.SetLength(std::min(tmp.Length(), 50u));
10253 0 : aResult += NS_LITERAL_STRING("\"") + NS_ConvertASCIItoUTF16(tmp) + NS_LITERAL_STRING("\"");
10254 0 : return NS_OK;
10255 : }
10256 :
10257 : void
10258 0 : nsTextFrame::List(FILE* out, const char* aPrefix, uint32_t aFlags) const
10259 : {
10260 0 : nsCString str;
10261 0 : ListGeneric(str, aPrefix, aFlags);
10262 :
10263 0 : str += nsPrintfCString(" [run=%p]", static_cast<void*>(mTextRun));
10264 :
10265 : // Output the first/last content offset and prev/next in flow info
10266 0 : bool isComplete = uint32_t(GetContentEnd()) == GetContent()->TextLength();
10267 0 : str += nsPrintfCString("[%d,%d,%c] ", GetContentOffset(), GetContentLength(),
10268 0 : isComplete ? 'T':'F');
10269 :
10270 0 : if (IsSelected()) {
10271 0 : str += " SELECTED";
10272 : }
10273 0 : fprintf_stderr(out, "%s\n", str.get());
10274 0 : }
10275 : #endif
10276 :
10277 : void
10278 0 : nsTextFrame::AdjustOffsetsForBidi(int32_t aStart, int32_t aEnd)
10279 : {
10280 0 : AddStateBits(NS_FRAME_IS_BIDI);
10281 0 : if (mContent->HasFlag(NS_HAS_FLOWLENGTH_PROPERTY)) {
10282 0 : mContent->DeleteProperty(nsGkAtoms::flowlength);
10283 0 : mContent->UnsetFlags(NS_HAS_FLOWLENGTH_PROPERTY);
10284 : }
10285 :
10286 : /*
10287 : * After Bidi resolution we may need to reassign text runs.
10288 : * This is called during bidi resolution from the block container, so we
10289 : * shouldn't be holding a local reference to a textrun anywhere.
10290 : */
10291 0 : ClearTextRuns();
10292 :
10293 0 : nsTextFrame* prev = GetPrevContinuation();
10294 0 : if (prev) {
10295 : // the bidi resolver can be very evil when columns/pages are involved. Don't
10296 : // let it violate our invariants.
10297 0 : int32_t prevOffset = prev->GetContentOffset();
10298 0 : aStart = std::max(aStart, prevOffset);
10299 0 : aEnd = std::max(aEnd, prevOffset);
10300 0 : prev->ClearTextRuns();
10301 : }
10302 :
10303 0 : mContentOffset = aStart;
10304 0 : SetLength(aEnd - aStart, nullptr, 0);
10305 0 : }
10306 :
10307 : /**
10308 : * @return true if this text frame ends with a newline character. It should return
10309 : * false if it is not a text frame.
10310 : */
10311 : bool
10312 0 : nsTextFrame::HasSignificantTerminalNewline() const
10313 : {
10314 0 : return ::HasTerminalNewline(this) && StyleText()->NewlineIsSignificant(this);
10315 : }
10316 :
10317 : bool
10318 0 : nsTextFrame::IsAtEndOfLine() const
10319 : {
10320 0 : return (GetStateBits() & TEXT_END_OF_LINE) != 0;
10321 : }
10322 :
10323 : nscoord
10324 37 : nsTextFrame::GetLogicalBaseline(WritingMode aWM) const
10325 : {
10326 0 : if (!aWM.IsOrthogonalTo(GetWritingMode())) {
10327 37 : return mAscent;
10328 : }
10329 :
10330 : // When the text frame has a writing mode orthogonal to the desired
10331 : // writing mode, return a baseline coincides its parent frame.
10332 0 : nsIFrame* parent = GetParent();
10333 0 : nsPoint position = GetNormalPosition();
10334 0 : nscoord parentAscent = parent->GetLogicalBaseline(aWM);
10335 0 : if (aWM.IsVerticalRL()) {
10336 0 : nscoord parentDescent = parent->GetSize().width - parentAscent;
10337 0 : nscoord descent = parentDescent - position.x;
10338 0 : return GetSize().width - descent;
10339 : }
10340 0 : return parentAscent - (aWM.IsVertical() ? position.x : position.y);
10341 : }
10342 :
10343 : bool
10344 0 : nsTextFrame::HasAnyNoncollapsedCharacters()
10345 : {
10346 0 : gfxSkipCharsIterator iter = EnsureTextRun(nsTextFrame::eInflated);
10347 0 : int32_t offset = GetContentOffset(),
10348 0 : offsetEnd = GetContentEnd();
10349 0 : int32_t skippedOffset = iter.ConvertOriginalToSkipped(offset);
10350 0 : int32_t skippedOffsetEnd = iter.ConvertOriginalToSkipped(offsetEnd);
10351 0 : return skippedOffset != skippedOffsetEnd;
10352 : }
10353 :
10354 : bool
10355 0 : nsTextFrame::ComputeCustomOverflow(nsOverflowAreas& aOverflowAreas)
10356 : {
10357 0 : if (GetStateBits() & NS_FRAME_FIRST_REFLOW) {
10358 : return true;
10359 : }
10360 :
10361 : nsIFrame* decorationsBlock;
10362 0 : if (IsFloatingFirstLetterChild()) {
10363 0 : decorationsBlock = GetParent();
10364 : } else {
10365 0 : nsIFrame* f = this;
10366 : for (;;) {
10367 0 : nsBlockFrame* fBlock = nsLayoutUtils::GetAsBlock(f);
10368 0 : if (fBlock) {
10369 : decorationsBlock = fBlock;
10370 : break;
10371 : }
10372 :
10373 0 : f = f->GetParent();
10374 0 : if (!f) {
10375 0 : NS_ERROR("Couldn't find any block ancestor (for text decorations)");
10376 0 : return nsFrame::ComputeCustomOverflow(aOverflowAreas);
10377 : }
10378 : }
10379 : }
10380 :
10381 0 : aOverflowAreas = RecomputeOverflow(decorationsBlock);
10382 0 : return nsFrame::ComputeCustomOverflow(aOverflowAreas);
10383 : }
10384 :
10385 : NS_DECLARE_FRAME_PROPERTY_SMALL_VALUE(JustificationAssignmentProperty, int32_t)
10386 :
10387 : void
10388 0 : nsTextFrame::AssignJustificationGaps(
10389 : const mozilla::JustificationAssignment& aAssign)
10390 : {
10391 0 : int32_t encoded = (aAssign.mGapsAtStart << 8) | aAssign.mGapsAtEnd;
10392 : static_assert(sizeof(aAssign) == 1,
10393 : "The encoding might be broken if JustificationAssignment "
10394 : "is larger than 1 byte");
10395 0 : SetProperty(JustificationAssignmentProperty(), encoded);
10396 0 : }
10397 :
10398 : mozilla::JustificationAssignment
10399 0 : nsTextFrame::GetJustificationAssignment() const
10400 : {
10401 0 : int32_t encoded = GetProperty(JustificationAssignmentProperty());
10402 0 : mozilla::JustificationAssignment result;
10403 0 : result.mGapsAtStart = encoded >> 8;
10404 0 : result.mGapsAtEnd = encoded & 0xFF;
10405 0 : return result;
10406 : }
10407 :
10408 : uint32_t
10409 0 : nsTextFrame::CountGraphemeClusters() const
10410 : {
10411 0 : const nsTextFragment* frag = GetContent()->GetText();
10412 0 : MOZ_ASSERT(frag, "Text frame must have text fragment");
10413 0 : nsAutoString content;
10414 0 : frag->AppendTo(content, GetContentOffset(), GetContentLength());
10415 0 : return unicode::CountGraphemeClusters(content.Data(), content.Length());
10416 : }
10417 :
10418 : bool
10419 0 : nsTextFrame::HasNonSuppressedText()
10420 : {
10421 0 : if (HasAnyStateBits(TEXT_ISNOT_ONLY_WHITESPACE |
10422 : // If we haven't reflowed yet, or are currently doing so,
10423 : // just return true because we can't be sure.
10424 : NS_FRAME_FIRST_REFLOW |
10425 : NS_FRAME_IN_REFLOW)) {
10426 : return true;
10427 : }
10428 :
10429 0 : if (!GetTextRun(nsTextFrame::eInflated)) {
10430 : return false;
10431 : }
10432 :
10433 0 : TrimmedOffsets offsets = GetTrimmedOffsets(mContent->GetText(), false);
10434 0 : return offsets.mLength != 0;
10435 : }
|