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