mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-29 07:42:04 +00:00
Bug 1063857 - Implement new justification algorithm. r=roc,masayuki
This commit is contained in:
parent
ebc042d630
commit
6ce2bfc5f9
143
layout/generic/JustificationUtils.h
Normal file
143
layout/generic/JustificationUtils.h
Normal 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_) */
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user