Bug 1063857 - Implement new justification algorithm. r=roc,masayuki

This commit is contained in:
Xidorn Quan 2014-11-02 15:07:00 +01:00
parent ebc042d630
commit 6ce2bfc5f9
5 changed files with 414 additions and 232 deletions

View File

@ -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_) */

View File

@ -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<nsTextFrame*>(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

View File

@ -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

View File

@ -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<JustificationAssignment> 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<uint32_t>(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;
}

View File

@ -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