diff --git a/layout/generic/JustificationUtils.h b/layout/generic/JustificationUtils.h new file mode 100644 index 000000000000..129ad0b6e1f5 --- /dev/null +++ b/layout/generic/JustificationUtils.h @@ -0,0 +1,143 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_JustificationUtils_h_ +#define mozilla_JustificationUtils_h_ + +#include "mozilla/Attributes.h" + +namespace mozilla { + +/** + * Jutification Algorithm + * + * The justification algorithm is based on expansion opportunities + * between justifiable clusters. By this algorithm, there is one + * expansion opportunity at each side of a justifiable cluster, and + * at most one opportunity between two clusters. For example, if there + * is a line in a Chinese document is: "你好世界hello world", then + * the expansion opportunities (marked as '*') would be: + * + * 你*好*世*界*hello*' '*world + * + * The spacing left in a line will then be distributed equally to each + * opportunities. Because we want that, only justifiable clusters get + * expanded, and the split point between two justifiable clusters would + * be at the middle of the spacing, each expansion opportunities will be + * filled by two justification gaps. The example above would be: + * + * 你 | 好 | 世 | 界 |hello| ' ' |world + * + * In the algorithm, information about expansion opportunities is stored + * in structure JustificationInfo, and the assignment of justification + * gaps is in structure JustificationAssignment. + */ + +struct JustificationInfo +{ + // Number of expansion opportunities inside a span. It doesn't include + // any opportunities between this span and the one before or after. + int32_t mInnerOpportunities; + // The justifiability of the start and end sides of the span. + bool mIsStartJustifiable; + bool mIsEndJustifiable; + + MOZ_CONSTEXPR JustificationInfo() + : mInnerOpportunities(0) + , mIsStartJustifiable(false) + , mIsEndJustifiable(false) + { + } + + // Claim that the last opportunity should be cancelled + // because the trailing space just gets trimmed. + void CancelOpportunityForTrimmedSpace() + { + if (mInnerOpportunities > 0) { + mInnerOpportunities--; + } else { + // There is no inner opportunities, hence the whole frame must + // contain only the trimmed space, because any content before + // space would cause an inner opportunity. The space made each + // side justifiable, which should be cancelled now. + mIsStartJustifiable = false; + mIsEndJustifiable = false; + } + } +}; + +struct JustificationAssignment +{ + // There are at most 2 gaps per end, so it is enough to use 2 bits. + uint8_t mGapsAtStart : 2; + uint8_t mGapsAtEnd : 2; + + MOZ_CONSTEXPR JustificationAssignment() + : mGapsAtStart(0) + , mGapsAtEnd(0) + { + } + + int32_t TotalGaps() const { return mGapsAtStart + mGapsAtEnd; } +}; + +struct JustificationApplicationState +{ + struct + { + // The total number of justification gaps to be processed. + int32_t mCount; + // The number of justification gaps which have been handled. + int32_t mHandled; + } mGaps; + + struct + { + // The total spacing left in a line before justification. + nscoord mAvailable; + // The spacing has been consumed by handled justification gaps. + nscoord mConsumed; + } mWidth; + + JustificationApplicationState(int32_t aGaps, nscoord aWidth) + { + mGaps.mCount = aGaps; + mGaps.mHandled = 0; + mWidth.mAvailable = aWidth; + mWidth.mConsumed = 0; + } + + bool IsJustifiable() const + { + return mGaps.mCount > 0 && mWidth.mAvailable > 0; + } + + nscoord Consume(int32_t aGaps) + { + mGaps.mHandled += aGaps; + nscoord newAllocate = (mWidth.mAvailable * mGaps.mHandled) / mGaps.mCount; + nscoord deltaWidth = newAllocate - mWidth.mConsumed; + mWidth.mConsumed = newAllocate; + return deltaWidth; + } +}; + +class JustificationUtils +{ +public: + // Compute justification gaps should be applied on a unit. + static int32_t CountGaps(const JustificationInfo& aInfo, + const JustificationAssignment& aAssign) + { + // Justification gaps include two gaps for each inner opportunities + // and the gaps given assigned to the ends. + return aInfo.mInnerOpportunities * 2 + aAssign.TotalGaps(); + } +}; + +} + +#endif /* !defined(mozilla_JustificationUtils_h_) */ diff --git a/layout/generic/nsLineLayout.cpp b/layout/generic/nsLineLayout.cpp index 349c9c429018..691aa1a9bc09 100644 --- a/layout/generic/nsLineLayout.cpp +++ b/layout/generic/nsLineLayout.cpp @@ -613,6 +613,9 @@ nsLineLayout::NewPerFrameData(nsIFrame* aFrame) pfd->mBorderPadding = LogicalMargin(frameWM); pfd->mOffsets = LogicalMargin(frameWM); + pfd->mJustificationInfo = JustificationInfo(); + pfd->mJustificationAssignment = JustificationAssignment(); + #ifdef DEBUG pfd->mBlockDirAlign = 0xFF; mFramesAllocated++; @@ -749,8 +752,7 @@ nsLineLayout::ReflowFrame(nsIFrame* aFrame, #endif } - mTextJustificationNumSpaces = 0; - mTextJustificationNumLetters = 0; + mJustificationInfo = JustificationInfo(); // Stash copies of some of the computed state away for later // (block-direction alignment, for example) @@ -865,9 +867,8 @@ nsLineLayout::ReflowFrame(nsIFrame* aFrame, ReflowText(*this, availableSpaceOnLine, psd->mReflowState->rendContext, metrics, aReflowStatus); } - - pfd->mJustificationNumSpaces = mTextJustificationNumSpaces; - pfd->mJustificationNumLetters = mTextJustificationNumLetters; + + pfd->mJustificationInfo = mJustificationInfo; // See if the frame is a placeholderFrame and if it is process // the float. At the same time, check if the frame has any non-collapsed-away @@ -934,6 +935,7 @@ nsLineLayout::ReflowFrame(nsIFrame* aFrame, } } } + pfd->SetFlag(PFD_ISEMPTY, isEmpty); mFloatManager->Untranslate(oldWM, tPt, mContainerWidth); @@ -2355,17 +2357,20 @@ nsLineLayout::TrimTrailingWhiteSpaceIn(PerSpanData* psd, nsFrame::ListTag(stdout, pfd->mFrame); printf(" returned %d\n", trimOutput.mDeltaWidth); #endif - if (trimOutput.mLastCharIsJustifiable && pfd->mJustificationNumSpaces > 0) { - pfd->mJustificationNumSpaces--; - } - + if (trimOutput.mChanged) { pfd->SetFlag(PFD_RECOMPUTEOVERFLOW, true); } + // Delta width not being zero means that + // there is trimmed space in the frame. if (trimOutput.mDeltaWidth) { pfd->mBounds.ISize(lineWM) -= trimOutput.mDeltaWidth; + // If any trailing space is trimmed, the justification opportunity + // generated by the space should be removed as well. + pfd->mJustificationInfo.CancelOpportunityForTrimmedSpace(); + // See if the text frame has already been placed in its parent if (psd != mRootSpan) { // The frame was already placed during psd's @@ -2418,43 +2423,71 @@ nsLineLayout::TrimTrailingWhiteSpace() return 0 != deltaISize; } -void -nsLineLayout::ComputeJustificationWeights(PerSpanData* aPSD, - int32_t* aNumSpaces, - int32_t* aNumLetters) +struct nsLineLayout::JustificationComputationState +{ + PerFrameData* mLastParticipant; +}; + +/** + * This function returns the total number of + * expansion opportunities in the given span. + */ +int32_t +nsLineLayout::ComputeFrameJustification(PerSpanData* aPSD, + JustificationComputationState& aState) { NS_ASSERTION(aPSD, "null arg"); - NS_ASSERTION(aNumSpaces, "null arg"); - NS_ASSERTION(aNumLetters, "null arg"); - int32_t numSpaces = 0; - int32_t numLetters = 0; + NS_ASSERTION(!aState.mLastParticipant || !aState.mLastParticipant->mSpan, + "Last participant shall always be a leaf frame"); + int32_t result = 0; - for (PerFrameData* pfd = aPSD->mFirstFrame; pfd != nullptr; pfd = pfd->mNext) { - - if (true == pfd->GetFlag(PFD_ISTEXTFRAME)) { - numSpaces += pfd->mJustificationNumSpaces; - numLetters += pfd->mJustificationNumLetters; + for (PerFrameData* pfd = aPSD->mFirstFrame; pfd; pfd = pfd->mNext) { + if (!pfd->ParticipatesInJustification()) { + continue; } - else if (pfd->mSpan != nullptr) { - int32_t spanSpaces; - int32_t spanLetters; - ComputeJustificationWeights(pfd->mSpan, &spanSpaces, &spanLetters); + if (pfd->mSpan) { + PerSpanData* span = pfd->mSpan; + result += ComputeFrameJustification(span, aState); + } else { + const auto& info = pfd->mJustificationInfo; + if (pfd->GetFlag(PFD_ISTEXTFRAME)) { + result += info.mInnerOpportunities; + } - numSpaces += spanSpaces; - numLetters += spanLetters; + PerFrameData* prev = aState.mLastParticipant; + if (prev) { + auto& assign = pfd->mJustificationAssignment; + auto& prevAssign = prev->mJustificationAssignment; + const auto& prevInfo = prev->mJustificationInfo; + + if (info.mIsStartJustifiable || prevInfo.mIsEndJustifiable) { + result++; + if (!info.mIsStartJustifiable) { + prevAssign.mGapsAtEnd = 2; + assign.mGapsAtStart = 0; + } else if (!prevInfo.mIsEndJustifiable) { + prevAssign.mGapsAtEnd = 0; + assign.mGapsAtStart = 2; + } else { + prevAssign.mGapsAtEnd = 1; + assign.mGapsAtStart = 1; + } + } + } + + aState.mLastParticipant = pfd; } } - *aNumSpaces = numSpaces; - *aNumLetters = numLetters; + return result; } nscoord -nsLineLayout::ApplyFrameJustification(PerSpanData* aPSD, FrameJustificationState* aState) +nsLineLayout::ApplyFrameJustification(PerSpanData* aPSD, + JustificationApplicationState& aState) { NS_ASSERTION(aPSD, "null arg"); - NS_ASSERTION(aState, "null arg"); nscoord deltaICoord = 0; for (PerFrameData* pfd = aPSD->mFirstFrame; pfd != nullptr; pfd = pfd->mNext) { @@ -2466,39 +2499,23 @@ nsLineLayout::ApplyFrameJustification(PerSpanData* aPSD, FrameJustificationState pfd->mBounds.IStart(lineWM) += deltaICoord; if (true == pfd->GetFlag(PFD_ISTEXTFRAME)) { - if (aState->mTotalWidthForSpaces > 0 && - aState->mTotalNumSpaces > 0) { - aState->mNumSpacesProcessed += pfd->mJustificationNumSpaces; - - nscoord newAllocatedWidthForSpaces = - (aState->mTotalWidthForSpaces*aState->mNumSpacesProcessed) - /aState->mTotalNumSpaces; - - dw += newAllocatedWidthForSpaces - aState->mWidthForSpacesProcessed; - - aState->mWidthForSpacesProcessed = newAllocatedWidthForSpaces; + if (aState.IsJustifiable()) { + // Set corresponding justification gaps here, so that the + // text frame knows how it should add gaps at its sides. + const auto& info = pfd->mJustificationInfo; + const auto& assign = pfd->mJustificationAssignment; + auto textFrame = static_cast(pfd->mFrame); + textFrame->AssignJustificationGaps(assign); + dw = aState.Consume(JustificationUtils::CountGaps(info, assign)); } - if (aState->mTotalWidthForLetters > 0 && - aState->mTotalNumLetters > 0) { - aState->mNumLettersProcessed += pfd->mJustificationNumLetters; - - nscoord newAllocatedWidthForLetters = - (aState->mTotalWidthForLetters*aState->mNumLettersProcessed) - /aState->mTotalNumLetters; - - dw += newAllocatedWidthForLetters - aState->mWidthForLettersProcessed; - - aState->mWidthForLettersProcessed = newAllocatedWidthForLetters; - } - if (dw) { pfd->SetFlag(PFD_RECOMPUTEOVERFLOW, true); } } else { if (nullptr != pfd->mSpan) { - dw += ApplyFrameJustification(pfd->mSpan, aState); + dw = ApplyFrameJustification(pfd->mSpan, aState); } } @@ -2559,24 +2576,29 @@ nsLineLayout::TextAlignLine(nsLineBox* aLine, !(mBlockReflowState->frame->IsSVGText())) { switch (textAlign) { - case NS_STYLE_TEXT_ALIGN_JUSTIFY: - int32_t numSpaces; - int32_t numLetters; - - ComputeJustificationWeights(psd, &numSpaces, &numLetters); - - if (numSpaces > 0) { - FrameJustificationState state = - { numSpaces, numLetters, remainingISize, 0, 0, 0, 0, 0 }; + case NS_STYLE_TEXT_ALIGN_JUSTIFY: { + JustificationComputationState computeState = { + nullptr // mLastParticipant + }; + int32_t opportunities = ComputeFrameJustification(psd, computeState); + if (opportunities > 0) { + JustificationApplicationState applyState( + opportunities * 2, remainingISize); // Apply the justification, and make sure to update our linebox // width to account for it. - aLine->ExpandBy(ApplyFrameJustification(psd, &state), + aLine->ExpandBy(ApplyFrameJustification(psd, applyState), ContainerWidthForSpan(psd)); + + MOZ_ASSERT(applyState.mGaps.mHandled == applyState.mGaps.mCount, + "Unprocessed justification gaps"); + MOZ_ASSERT(applyState.mWidth.mConsumed == applyState.mWidth.mAvailable, + "Unprocessed justification width"); break; } // Fall through to the default case if we could not justify to fill // the space. + } case NS_STYLE_TEXT_ALIGN_DEFAULT: // default alignment is to start edge so do nothing diff --git a/layout/generic/nsLineLayout.h b/layout/generic/nsLineLayout.h index 3d8a2eadc8bb..4672921576f1 100644 --- a/layout/generic/nsLineLayout.h +++ b/layout/generic/nsLineLayout.h @@ -22,6 +22,7 @@ #include "plarena.h" #include "gfxTypes.h" #include "WritingModes.h" +#include "JustificationUtils.h" class nsFloatManager; struct nsStyleText; @@ -112,9 +113,9 @@ public: // Support methods for word-wrapping during line reflow - void SetTextJustificationWeights(int32_t aNumSpaces, int32_t aNumLetters) { - mTextJustificationNumSpaces = aNumSpaces; - mTextJustificationNumLetters = aNumLetters; + void SetJustificationInfo(const mozilla::JustificationInfo& aInfo) + { + mJustificationInfo = aInfo; } /** @@ -389,12 +390,9 @@ protected: mozilla::LogicalMargin mOffsets; // state for text justification - int32_t mJustificationNumSpaces; - int32_t mJustificationNumLetters; + mozilla::JustificationInfo mJustificationInfo; + mozilla::JustificationAssignment mJustificationAssignment; - // Other state we use - uint8_t mBlockDirAlign; - // PerFrameData flags #define PFD_RELATIVEPOS 0x00000001 #define PFD_ISTEXTFRAME 0x00000002 @@ -404,14 +402,19 @@ protected: #define PFD_RECOMPUTEOVERFLOW 0x00000020 #define PFD_ISBULLET 0x00000040 #define PFD_SKIPWHENTRIMMINGWHITESPACE 0x00000080 -#define PFD_LASTFLAG PFD_SKIPWHENTRIMMINGWHITESPACE +#define PFD_ISEMPTY 0x00000100 +#define PFD_LASTFLAG PFD_ISEMPTY - uint8_t mFlags; + // Other state we use + uint16_t mFlags; + uint8_t mBlockDirAlign; + + static_assert(PFD_LASTFLAG <= UINT16_MAX, + "Flag value exceeds the length of flags variable."); void SetFlag(uint32_t aFlag, bool aValue) { NS_ASSERTION(aFlag<=PFD_LASTFLAG, "bad flag"); - NS_ASSERTION(aFlag<=UINT8_MAX, "bad flag"); if (aValue) { // set flag mFlags |= aFlag; } @@ -434,6 +437,22 @@ protected: } return pfd; } + + bool IsStartJustifiable() const + { + return mJustificationInfo.mIsStartJustifiable; + } + + bool IsEndJustifiable() const + { + return mJustificationInfo.mIsEndJustifiable; + } + + bool ParticipatesInJustification() const + { + // Skip bullets and empty frames + return !GetFlag(PFD_ISBULLET) && !GetFlag(PFD_ISEMPTY); + } }; PerFrameData* mFrameFreeList; @@ -500,8 +519,7 @@ protected: // This state varies during the reflow of a line but is line // "global" state not span "local" state. int32_t mLineNumber; - int32_t mTextJustificationNumSpaces; - int32_t mTextJustificationNumLetters; + mozilla::JustificationInfo mJustificationInfo; int32_t mTotalPlacedFrames; @@ -584,23 +602,14 @@ protected: bool TrimTrailingWhiteSpaceIn(PerSpanData* psd, nscoord* aDeltaISize); - void ComputeJustificationWeights(PerSpanData* psd, int32_t* numSpaces, int32_t* numLetters); - - struct FrameJustificationState { - int32_t mTotalNumSpaces; - int32_t mTotalNumLetters; - nscoord mTotalWidthForSpaces; - nscoord mTotalWidthForLetters; - int32_t mNumSpacesProcessed; - int32_t mNumLettersProcessed; - nscoord mWidthForSpacesProcessed; - nscoord mWidthForLettersProcessed; - }; + struct JustificationComputationState; + int32_t ComputeFrameJustification(PerSpanData* psd, + JustificationComputationState& aState); // Apply justification. The return value is the amount by which the width of // the span corresponding to aPSD got increased due to justification. - nscoord ApplyFrameJustification(PerSpanData* aPSD, - FrameJustificationState* aState); + nscoord ApplyFrameJustification( + PerSpanData* aPSD, mozilla::JustificationApplicationState& aState); #ifdef DEBUG diff --git a/layout/generic/nsTextFrame.cpp b/layout/generic/nsTextFrame.cpp index 7a334383bbcb..80d2b5bd4404 100644 --- a/layout/generic/nsTextFrame.cpp +++ b/layout/generic/nsTextFrame.cpp @@ -2808,9 +2808,9 @@ public: mLength(aLength), mWordSpacing(WordSpacing(aFrame, aTextStyle)), mLetterSpacing(LetterSpacing(aFrame, aTextStyle)), - mJustificationSpacing(0), mHyphenWidth(-1), mOffsetFromBlockOriginForTabs(aOffsetFromBlockOriginForTabs), + mJustificationSpacing(0), mReflowing(true), mWhichTextRun(aWhichTextRun) { @@ -2833,9 +2833,9 @@ public: mLength(aFrame->GetContentLength()), mWordSpacing(WordSpacing(aFrame)), mLetterSpacing(LetterSpacing(aFrame)), - mJustificationSpacing(0), mHyphenWidth(-1), mOffsetFromBlockOriginForTabs(0), + mJustificationSpacing(0), mReflowing(false), mWhichTextRun(aWhichTextRun) { @@ -2867,16 +2867,10 @@ public: bool aIgnoreTabs); /** - * Count the number of justifiable characters in the given DOM range + * Compute the justification information in given DOM range, and fill data + * necessary for computation of spacing. */ - uint32_t ComputeJustifiableCharacters(int32_t aOffset, int32_t aLength); - /** - * Find the start and end of the justifiable characters. Does not depend on the - * position of aStart or aEnd, although it's most efficient if they are near the - * start and end of the text frame. - */ - void FindJustificationRange(gfxSkipCharsIterator* aStart, - gfxSkipCharsIterator* aEnd); + void ComputeJustification(int32_t aOffset, int32_t aLength); const nsStyleText* StyleText() { return mTextStyle; } nsTextFrame* GetFrame() { return mFrame; } @@ -2907,6 +2901,11 @@ public: const gfxSkipCharsIterator& GetEndHint() { return mTempIterator; } + const JustificationInfo& GetJustificationInfo() const + { + return mJustificationInfo; + } + protected: void SetupJustificationSpacing(bool aPostReflow); @@ -2937,31 +2936,22 @@ protected: int32_t mLength; // DOM string length, may be INT32_MAX gfxFloat mWordSpacing; // space for each whitespace char gfxFloat mLetterSpacing; // space for each letter - gfxFloat mJustificationSpacing; gfxFloat mHyphenWidth; gfxFloat mOffsetFromBlockOriginForTabs; + + // The total spacing for justification + gfxFloat mJustificationSpacing; + int32_t mTotalJustificationGaps; + JustificationInfo mJustificationInfo; + // The values in mJustificationAssignments corresponds to unskipped + // characters start from mJustificationArrayStart. + uint32_t mJustificationArrayStart; + nsTArray mJustificationAssignments; + bool mReflowing; nsTextFrame::TextRunType mWhichTextRun; }; -uint32_t -PropertyProvider::ComputeJustifiableCharacters(int32_t aOffset, int32_t aLength) -{ - // Scan non-skipped characters and count justifiable chars. - nsSkipCharsRunIterator - run(mStart, nsSkipCharsRunIterator::LENGTH_INCLUDES_SKIPPED, aLength); - run.SetOriginalOffset(aOffset); - uint32_t justifiableChars = 0; - bool isCJ = IsChineseOrJapanese(mFrame); - while (run.NextRun()) { - for (int32_t i = 0; i < run.GetRunLength(); ++i) { - justifiableChars += - IsJustifiableCharacter(mFrag, run.GetOriginalOffset() + i, isCJ); - } - } - return justifiableChars; -} - /** * Finds the offset of the first character of the cluster containing aPos */ @@ -3001,6 +2991,73 @@ static void FindClusterEnd(gfxTextRun* aTextRun, int32_t aOriginalEnd, aPos->AdvanceOriginal(-1); } +void +PropertyProvider::ComputeJustification(int32_t aOffset, int32_t aLength) +{ + bool isCJ = IsChineseOrJapanese(mFrame); + nsSkipCharsRunIterator + run(mStart, nsSkipCharsRunIterator::LENGTH_INCLUDES_SKIPPED, aLength); + run.SetOriginalOffset(aOffset); + mJustificationArrayStart = run.GetSkippedOffset(); + + MOZ_ASSERT(mJustificationAssignments.IsEmpty()); + mJustificationAssignments.SetCapacity(aLength); + while (run.NextRun()) { + uint32_t originalOffset = run.GetOriginalOffset(); + uint32_t skippedOffset = run.GetSkippedOffset(); + uint32_t length = run.GetRunLength(); + mJustificationAssignments.SetLength( + skippedOffset + length - mJustificationArrayStart); + + gfxSkipCharsIterator iter = run.GetPos(); + for (uint32_t i = 0; i < length; ++i) { + uint32_t offset = originalOffset + i; + if (!IsJustifiableCharacter(mFrag, offset, isCJ)) { + continue; + } + + iter.SetOriginalOffset(offset); + + FindClusterStart(mTextRun, originalOffset, &iter); + uint32_t firstCharOffset = iter.GetSkippedOffset(); + uint32_t firstChar = firstCharOffset > mJustificationArrayStart ? + firstCharOffset - mJustificationArrayStart : 0; + if (!firstChar) { + mJustificationInfo.mIsStartJustifiable = true; + } else { + auto& assign = mJustificationAssignments[firstChar]; + auto& prevAssign = mJustificationAssignments[firstChar - 1]; + if (prevAssign.mGapsAtEnd) { + prevAssign.mGapsAtEnd = 1; + assign.mGapsAtStart = 1; + } else { + assign.mGapsAtStart = 2; + mJustificationInfo.mInnerOpportunities++; + } + } + + FindClusterEnd(mTextRun, originalOffset + length, &iter); + uint32_t lastChar = iter.GetSkippedOffset() - mJustificationArrayStart; + // Assign the two gaps temporary to the last char. If the next cluster is + // justifiable as well, one of the gaps will be removed by code above. + mJustificationAssignments[lastChar].mGapsAtEnd = 2; + mJustificationInfo.mInnerOpportunities++; + + // Skip the whole cluster + i = iter.GetOriginalOffset() - originalOffset; + } + } + + if (!mJustificationAssignments.IsEmpty() && + mJustificationAssignments.LastElement().mGapsAtEnd) { + // We counted the expansion opportunity after the last character, + // but it is not an inner opportunity. + MOZ_ASSERT(mJustificationInfo.mInnerOpportunities > 0); + mJustificationInfo.mInnerOpportunities--; + mJustificationInfo.mIsEndJustifiable = true; + } +} + // aStart, aLength in transformed string offsets void PropertyProvider::GetSpacing(uint32_t aStart, uint32_t aLength, @@ -3075,35 +3132,21 @@ PropertyProvider::GetSpacingInternal(uint32_t aStart, uint32_t aLength, } // Now add in justification spacing - if (mJustificationSpacing) { - gfxFloat halfJustificationSpace = mJustificationSpacing/2; - // Scan non-skipped characters and adjust justifiable chars, adding - // justification space on either side of the cluster - bool isCJ = IsChineseOrJapanese(mFrame); - gfxSkipCharsIterator justificationStart(mStart), justificationEnd(mStart); - FindJustificationRange(&justificationStart, &justificationEnd); - - nsSkipCharsRunIterator - run(start, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength); - while (run.NextRun()) { - gfxSkipCharsIterator iter = run.GetPos(); - int32_t runOriginalOffset = run.GetOriginalOffset(); - for (int32_t i = 0; i < run.GetRunLength(); ++i) { - int32_t iterOriginalOffset = runOriginalOffset + i; - if (IsJustifiableCharacter(mFrag, iterOriginalOffset, isCJ)) { - iter.SetOriginalOffset(iterOriginalOffset); - FindClusterStart(mTextRun, runOriginalOffset, &iter); - uint32_t clusterFirstChar = iter.GetSkippedOffset(); - FindClusterEnd(mTextRun, runOriginalOffset + run.GetRunLength(), &iter); - uint32_t clusterLastChar = iter.GetSkippedOffset(); - // Only apply justification to characters before justificationEnd - if (clusterFirstChar >= justificationStart.GetSkippedOffset() && - clusterLastChar < justificationEnd.GetSkippedOffset()) { - aSpacing[clusterFirstChar - aStart].mBefore += halfJustificationSpace; - aSpacing[clusterLastChar - aStart].mAfter += halfJustificationSpace; - } - } - } + if (mJustificationSpacing > 0 && mTotalJustificationGaps) { + // If there is any spaces trimmed at the end, aStart + aLength may + // be larger than the flags array. When that happens, we can simply + // ignore those spaces. + auto arrayEnd = mJustificationArrayStart + + static_cast(mJustificationAssignments.Length()); + auto end = std::min(aStart + aLength, arrayEnd); + MOZ_ASSERT(aStart >= mJustificationArrayStart); + JustificationApplicationState state( + mTotalJustificationGaps, NSToCoordRound(mJustificationSpacing)); + for (auto i = aStart; i < end; i++) { + const auto& assign = + mJustificationAssignments[i - mJustificationArrayStart]; + aSpacing[i - aStart].mBefore += state.Consume(assign.mGapsAtStart); + aSpacing[i - aStart].mAfter += state.Consume(assign.mGapsAtEnd); } } } @@ -3311,37 +3354,6 @@ static uint32_t GetSkippedDistance(const gfxSkipCharsIterator& aStart, return aEnd.GetSkippedOffset() - aStart.GetSkippedOffset(); } -void -PropertyProvider::FindJustificationRange(gfxSkipCharsIterator* aStart, - gfxSkipCharsIterator* aEnd) -{ - NS_PRECONDITION(mLength != INT32_MAX, "Can't call this with undefined length"); - NS_ASSERTION(aStart && aEnd, "aStart or/and aEnd is null"); - - aStart->SetOriginalOffset(mStart.GetOriginalOffset()); - aEnd->SetOriginalOffset(mStart.GetOriginalOffset() + mLength); - - // Ignore first cluster at start of line for justification purposes - if (mFrame->GetStateBits() & TEXT_START_OF_LINE) { - while (aStart->GetOriginalOffset() < aEnd->GetOriginalOffset()) { - aStart->AdvanceOriginal(1); - if (!aStart->IsOriginalCharSkipped() && - mTextRun->IsClusterStart(aStart->GetSkippedOffset())) - break; - } - } - - // Ignore trailing cluster at end of line for justification purposes - if (mFrame->GetStateBits() & TEXT_END_OF_LINE) { - while (aEnd->GetOriginalOffset() > aStart->GetOriginalOffset()) { - aEnd->AdvanceOriginal(-1); - if (!aEnd->IsOriginalCharSkipped() && - mTextRun->IsClusterStart(aEnd->GetSkippedOffset())) - break; - } - } -} - void PropertyProvider::SetupJustificationSpacing(bool aPostReflow) { @@ -3358,12 +3370,13 @@ PropertyProvider::SetupJustificationSpacing(bool aPostReflow) mFrame->GetTrimmedOffsets(mFrag, true, aPostReflow); end.AdvanceOriginal(trimmed.mLength); gfxSkipCharsIterator realEnd(end); - FindJustificationRange(&start, &end); + ComputeJustification(start.GetOriginalOffset(), + end.GetOriginalOffset() - start.GetOriginalOffset()); - int32_t justifiableCharacters = - ComputeJustifiableCharacters(start.GetOriginalOffset(), - end.GetOriginalOffset() - start.GetOriginalOffset()); - if (justifiableCharacters == 0) { + auto assign = mFrame->GetJustificationAssignment(); + mTotalJustificationGaps = + JustificationUtils::CountGaps(mJustificationInfo, assign); + if (!mTotalJustificationGaps || mJustificationAssignments.IsEmpty()) { // Nothing to do, nothing is justifiable and we shouldn't have any // justification space assigned return; @@ -3375,13 +3388,14 @@ PropertyProvider::SetupJustificationSpacing(bool aPostReflow) if (mFrame->GetStateBits() & TEXT_HYPHEN_BREAK) { naturalWidth += GetHyphenWidth(); } - gfxFloat totalJustificationSpace = mFrame->GetSize().width - naturalWidth; - if (totalJustificationSpace <= 0) { + mJustificationSpacing = mFrame->GetSize().width - naturalWidth; + if (mJustificationSpacing <= 0) { // No space available return; } - - mJustificationSpacing = totalJustificationSpace/justifiableCharacters; + + mJustificationAssignments[0].mGapsAtStart = assign.mGapsAtStart; + mJustificationAssignments.LastElement().mGapsAtEnd = assign.mGapsAtEnd; } //---------------------------------------------------------------------- @@ -8391,15 +8405,9 @@ nsTextFrame::ReflowText(nsLineLayout& aLineLayout, nscoord aAvailableWidth, (lineContainer->StyleText()->mTextAlign == NS_STYLE_TEXT_ALIGN_JUSTIFY || lineContainer->StyleText()->mTextAlignLast == NS_STYLE_TEXT_ALIGN_JUSTIFY) && !lineContainer->IsSVGText()) { - AddStateBits(TEXT_JUSTIFICATION_ENABLED); // This will include a space for trailing whitespace, if any is present. - // This is corrected for in nsLineLayout::TrimWhiteSpaceIn. - int32_t numJustifiableCharacters = - provider.ComputeJustifiableCharacters(offset, charsFit); - - NS_ASSERTION(numJustifiableCharacters <= charsFit, - "Bad justifiable character count"); - aLineLayout.SetTextJustificationWeights(numJustifiableCharacters, - charsFit - numJustifiableCharacters); + AddStateBits(TEXT_JUSTIFICATION_ENABLED); + provider.ComputeJustification(offset, charsFit); + aLineLayout.SetJustificationInfo(provider.GetJustificationInfo()); } SetLength(contentLength, &aLineLayout, ALLOW_FRAME_CREATION_AND_DESTRUCTION); @@ -8426,7 +8434,6 @@ nsTextFrame::TrimTrailingWhiteSpace(nsRenderingContext* aRC) { TrimOutput result; result.mChanged = false; - result.mLastCharIsJustifiable = false; result.mDeltaWidth = 0; AddStateBits(TEXT_END_OF_LINE); @@ -8449,10 +8456,8 @@ nsTextFrame::TrimTrailingWhiteSpace(nsRenderingContext* aRC) gfxFloat delta = 0; uint32_t trimmedEnd = trimmedEndIter.ConvertOriginalToSkipped(trimmed.GetEnd()); - if (GetStateBits() & TEXT_TRIMMED_TRAILING_WHITESPACE) { - // We pre-trimmed this frame, so the last character is justifiable - result.mLastCharIsJustifiable = true; - } else if (trimmed.GetEnd() < GetContentEnd()) { + if (!(GetStateBits() & TEXT_TRIMMED_TRAILING_WHITESPACE) && + trimmed.GetEnd() < GetContentEnd()) { gfxSkipCharsIterator end = trimmedEndIter; uint32_t endOffset = end.ConvertOriginalToSkipped(GetContentOffset() + contentLength); if (trimmedEnd < endOffset) { @@ -8461,31 +8466,10 @@ nsTextFrame::TrimTrailingWhiteSpace(nsRenderingContext* aRC) PropertyProvider provider(mTextRun, textStyle, frag, this, start, contentLength, nullptr, 0, nsTextFrame::eInflated); delta = mTextRun->GetAdvanceWidth(trimmedEnd, endOffset - trimmedEnd, &provider); - // non-compressed whitespace being skipped at end of line -> justifiable - // XXX should we actually *count* justifiable characters that should be - // removed from the overall count? I think so... - result.mLastCharIsJustifiable = true; result.mChanged = true; } } - if (!result.mLastCharIsJustifiable && - (GetStateBits() & TEXT_JUSTIFICATION_ENABLED)) { - // Check if any character in the last cluster is justifiable - PropertyProvider provider(mTextRun, textStyle, frag, this, start, contentLength, - nullptr, 0, nsTextFrame::eInflated); - bool isCJ = IsChineseOrJapanese(this); - gfxSkipCharsIterator justificationStart(start), justificationEnd(trimmedEndIter); - provider.FindJustificationRange(&justificationStart, &justificationEnd); - - for (int32_t i = justificationEnd.GetOriginalOffset(); - i < trimmed.GetEnd(); ++i) { - if (IsJustifiableCharacter(frag, i, isCJ)) { - result.mLastCharIsJustifiable = true; - } - } - } - gfxFloat advanceDelta; mTextRun->SetLineBreaks(trimmedStart, trimmedEnd - trimmedStart, (GetStateBits() & TEXT_START_OF_LINE) != 0, true, @@ -8880,3 +8864,25 @@ nsTextFrame::UpdateOverflow() &overflowAreas.VisualOverflow(), true); return FinishAndStoreOverflow(overflowAreas, GetSize()); } + +void +nsTextFrame::AssignJustificationGaps( + const mozilla::JustificationAssignment& aAssign) +{ + int32_t encoded = (aAssign.mGapsAtStart << 8) | aAssign.mGapsAtEnd; + static_assert(sizeof(aAssign) == 1, + "The encoding might be broken if JustificationAssignment " + "is larger than 1 byte"); + Properties().Set(JustificationAssignment(), NS_INT32_TO_PTR(encoded)); +} + +mozilla::JustificationAssignment +nsTextFrame::GetJustificationAssignment() const +{ + int32_t encoded = + NS_PTR_TO_INT32(Properties().Get(JustificationAssignment())); + mozilla::JustificationAssignment result; + result.mGapsAtStart = encoded >> 8; + result.mGapsAtEnd = encoded & 0xFF; + return result; +} diff --git a/layout/generic/nsTextFrame.h b/layout/generic/nsTextFrame.h index 486e26f92608..fb9ffb46bcaa 100644 --- a/layout/generic/nsTextFrame.h +++ b/layout/generic/nsTextFrame.h @@ -13,6 +13,7 @@ #include "gfxSkipChars.h" #include "gfxTextRun.h" #include "nsDisplayList.h" +#include "JustificationUtils.h" class nsTextPaintStyle; class PropertyProvider; @@ -234,10 +235,6 @@ public: // true if we trimmed some space or changed metrics in some other way. // In this case, we should call RecomputeOverflow on this frame. bool mChanged; - // true if the last character is not justifiable so should be subtracted - // from the count of justifiable characters in the frame, since the last - // character in a line is not justifiable. - bool mLastCharIsJustifiable; // an amount to *subtract* from the frame's width (zero if !mChanged) nscoord mDeltaWidth; }; @@ -528,6 +525,9 @@ public: virtual bool UpdateOverflow() MOZ_OVERRIDE; + void AssignJustificationGaps(const mozilla::JustificationAssignment& aAssign); + mozilla::JustificationAssignment GetJustificationAssignment() const; + protected: virtual ~nsTextFrame(); @@ -710,6 +710,8 @@ protected: virtual bool HasAnyNoncollapsedCharacters() MOZ_OVERRIDE; void ClearMetrics(nsHTMLReflowMetrics& aMetrics); + + NS_DECLARE_FRAME_PROPERTY(JustificationAssignment, nullptr) }; #endif