Bug 1056516 - let auto hyphen honor manual hyphen when hyphens:auto is set. r=jfkthame

MozReview-Commit-ID: F3ALAJRHned
This commit is contained in:
Jeremy Chen 2017-03-13 12:54:05 +08:00
Родитель 2e09040e6e
Коммит 0316fede8c
3 изменённых файлов: 172 добавлений и 10 удалений

Просмотреть файл

@ -841,6 +841,73 @@ gfxTextRun::MeasureText(Range aRange,
#define MEASUREMENT_BUFFER_SIZE 100
void
gfxTextRun::ClassifyAutoHyphenations(uint32_t aStart, Range aRange,
nsTArray<HyphenType>& aHyphenBuffer,
HyphenationState* aWordState)
{
NS_PRECONDITION(aRange.end - aStart <= aHyphenBuffer.Length() &&
aRange.start >= aStart, "Range out of bounds");
MOZ_ASSERT(aWordState->mostRecentBoundary >= aStart,
"Unexpected aMostRecentWordBoundary!!");
uint32_t start = std::min<uint32_t>(aRange.start, aWordState->mostRecentBoundary);
for (uint32_t i = start; i < aRange.end; ++i) {
if (aHyphenBuffer[i - aStart] == HyphenType::Explicit &&
!aWordState->hasExplicitHyphen) {
aWordState->hasExplicitHyphen = true;
}
if (!aWordState->hasManualHyphen &&
(aHyphenBuffer[i - aStart] == HyphenType::Soft ||
aHyphenBuffer[i - aStart] == HyphenType::Explicit)) {
aWordState->hasManualHyphen = true;
// This is the first manual hyphen in the current word. We can only
// know if the current word has a manual hyphen until now. So, we need
// to run a sub loop to update the auto hyphens between the start of
// the current word and this manual hyphen.
if (aWordState->hasAutoHyphen) {
for (uint32_t j = aWordState->mostRecentBoundary; j < i; j++) {
if (aHyphenBuffer[j - aStart] == HyphenType::AutoWithoutManualInSameWord) {
aHyphenBuffer[j - aStart] = HyphenType::AutoWithManualInSameWord;
}
}
}
}
if (aHyphenBuffer[i - aStart] == HyphenType::AutoWithoutManualInSameWord) {
if (!aWordState->hasAutoHyphen) {
aWordState->hasAutoHyphen = true;
}
if (aWordState->hasManualHyphen) {
aHyphenBuffer[i - aStart] = HyphenType::AutoWithManualInSameWord;
}
}
// If we're at the word boundary, clear/reset couple states.
if (mCharacterGlyphs[i].CharIsSpace() ||
mCharacterGlyphs[i].CharIsTab() ||
mCharacterGlyphs[i].CharIsNewline() ||
// Since we will not have a boundary in the end of the string, let's
// call the end of the string a special case for word boundary.
i == GetLength() - 1) {
// We can only get to know whether we should raise/clear an explicit
// manual hyphen until we get to the end of a word, because this depends
// on whether there exists at least one auto hyphen in the same word.
if (!aWordState->hasAutoHyphen && aWordState->hasExplicitHyphen) {
for (uint32_t j = aWordState->mostRecentBoundary; j <= i; j++) {
if (aHyphenBuffer[j - aStart] == HyphenType::Explicit) {
aHyphenBuffer[j - aStart] = HyphenType::None;
}
}
}
aWordState->mostRecentBoundary = i;
aWordState->hasManualHyphen = false;
aWordState->hasAutoHyphen = false;
aWordState->hasExplicitHyphen = false;
}
}
}
uint32_t
gfxTextRun::BreakAndMeasureText(uint32_t aStart, uint32_t aMaxLength,
bool aLineBreakBefore, gfxFloat aWidth,
@ -868,6 +935,8 @@ gfxTextRun::BreakAndMeasureText(uint32_t aStart, uint32_t aMaxLength,
GetAdjustedSpacing(this, bufferRange, aProvider, spacingBuffer);
}
AutoTArray<HyphenType, 4096> hyphenBuffer;
HyphenationState wordState;
wordState.mostRecentBoundary = aStart;
bool haveHyphenation = aProvider &&
(aProvider->GetHyphensOption() == StyleHyphens::Auto ||
(aProvider->GetHyphensOption() == StyleHyphens::Manual &&
@ -875,6 +944,10 @@ gfxTextRun::BreakAndMeasureText(uint32_t aStart, uint32_t aMaxLength,
if (haveHyphenation) {
if (hyphenBuffer.AppendElements(bufferRange.Length(), fallible)) {
aProvider->GetHyphenationBreaks(bufferRange, hyphenBuffer.Elements());
if (aProvider->GetHyphensOption() == StyleHyphens::Auto) {
ClassifyAutoHyphenations(aStart, bufferRange, hyphenBuffer,
&wordState);
}
} else {
haveHyphenation = false;
}
@ -889,15 +962,23 @@ gfxTextRun::BreakAndMeasureText(uint32_t aStart, uint32_t aMaxLength,
int32_t lastBreak = -1;
int32_t lastBreakTrimmableChars = -1;
gfxFloat lastBreakTrimmableAdvance = -1;
// Cache the last candidate break
int32_t lastCandidateBreak = -1;
int32_t lastCandidateBreakTrimmableChars = -1;
gfxFloat lastCandidateBreakTrimmableAdvance = -1;
bool lastCandidateBreakUsedHyphenation = false;
gfxBreakPriority lastCandidateBreakPriority = gfxBreakPriority::eNoBreak;
bool aborted = false;
uint32_t end = aStart + aMaxLength;
bool lastBreakUsedHyphenation = false;
Range ligatureRange(aStart, end);
ShrinkToLigatureBoundaries(&ligatureRange);
uint32_t i;
for (i = aStart; i < end; ++i) {
// We may need to move `i` backwards in the following loop, and re-scan
// part of the textrun; we'll use `rescanLimit` so we can tell when that
// is happening: if `i < rescanLimit` then we're rescanning.
uint32_t rescanLimit = aStart;
for (uint32_t i = aStart; i < end; ++i) {
if (i >= bufferRange.end) {
// Fetch more spacing and hyphenation data
uint32_t oldHyphenBufferLength = hyphenBuffer.Length();
@ -920,6 +1001,20 @@ gfxTextRun::BreakAndMeasureText(uint32_t aStart, uint32_t aMaxLength,
if (hyphenBuffer.AppendElements(bufferRange.Length(), fallible)) {
aProvider->GetHyphenationBreaks(
bufferRange, hyphenBuffer.Elements() + oldHyphenBufferLength);
if (aProvider->GetHyphensOption() == StyleHyphens::Auto) {
uint32_t prevMostRecentWordBoundary = wordState.mostRecentBoundary;
ClassifyAutoHyphenations(aStart, bufferRange, hyphenBuffer,
&wordState);
// If the buffer boundary is in the middle of a word,
// we need to go back to the start of the current word.
// So, we can correct the wrong candidates that we set
// in the previous runs of the loop.
if (prevMostRecentWordBoundary < oldHyphenBufferLength) {
rescanLimit = i;
i = prevMostRecentWordBoundary - 1;
continue;
}
}
} else {
haveHyphenation = false;
}
@ -935,6 +1030,8 @@ gfxTextRun::BreakAndMeasureText(uint32_t aStart, uint32_t aMaxLength,
bool atNaturalBreak = mCharacterGlyphs[i].CanBreakBefore() == 1;
bool atHyphenationBreak = !atNaturalBreak && haveHyphenation &&
hyphenBuffer[i - aStart] != HyphenType::None;
bool atAutoHyphenWithManualHyphenInSameWord = atHyphenationBreak &&
hyphenBuffer[i - aStart] == HyphenType::AutoWithManualInSameWord;
bool atBreak = atNaturalBreak || atHyphenationBreak;
bool wordWrapping =
aCanWordWrap && mCharacterGlyphs[i].IsClusterStart() &&
@ -946,7 +1043,8 @@ gfxTextRun::BreakAndMeasureText(uint32_t aStart, uint32_t aMaxLength,
hyphenatedAdvance += aProvider->GetHyphenWidth();
}
if (lastBreak < 0 || width + hyphenatedAdvance - trimmableAdvance <= aWidth) {
if (lastBreak < 0 ||
width + hyphenatedAdvance - trimmableAdvance <= aWidth) {
// We can break here.
lastBreak = i;
lastBreakTrimmableChars = trimmableChars;
@ -963,8 +1061,35 @@ gfxTextRun::BreakAndMeasureText(uint32_t aStart, uint32_t aMaxLength,
aborted = true;
break;
}
// There are various kinds of break opportunities:
// 1. word wrap break,
// 2. natural break,
// 3. manual hyphenation break,
// 4. auto hyphenation break without any manual hyphenation
// in the same word,
// 5. auto hyphenation break with another manual hyphenation
// in the same word.
// Allow all of them except the last one to be a candidate.
// So, we can ensure that we don't use an automatic
// hyphenation opportunity within a word that contains another
// manual hyphenation, unless it is the only choice.
if (wordWrapping ||
!atAutoHyphenWithManualHyphenInSameWord) {
lastCandidateBreak = lastBreak;
lastCandidateBreakTrimmableChars = lastBreakTrimmableChars;
lastCandidateBreakTrimmableAdvance = lastBreakTrimmableAdvance;
lastCandidateBreakUsedHyphenation = lastBreakUsedHyphenation;
lastCandidateBreakPriority = *aBreakPriority;
}
}
}
// If we're re-scanning part of a word (to re-process potential
// hyphenation types) then we don't want to accumulate widths again
// for the characters that were already added to `advance`.
if (i < rescanLimit) {
continue;
}
gfxFloat charAdvance;
if (i >= ligatureRange.start && i < ligatureRange.end) {
@ -1004,6 +1129,13 @@ gfxTextRun::BreakAndMeasureText(uint32_t aStart, uint32_t aMaxLength,
if (width - trimmableAdvance <= aWidth) {
charsFit = aMaxLength;
} else if (lastBreak >= 0) {
if (lastCandidateBreak >= 0 && lastCandidateBreak != lastBreak) {
lastBreak = lastCandidateBreak;
lastBreakTrimmableChars = lastCandidateBreakTrimmableChars;
lastBreakTrimmableAdvance = lastCandidateBreakTrimmableAdvance;
lastBreakUsedHyphenation = lastCandidateBreakUsedHyphenation;
*aBreakPriority = lastCandidateBreakPriority;
}
charsFit = lastBreak - aStart;
trimmableChars = lastBreakTrimmableChars;
trimmableAdvance = lastBreakTrimmableAdvance;

Просмотреть файл

@ -177,8 +177,17 @@ public:
enum class HyphenType : uint8_t {
None,
Manual,
Auto
Explicit,
Soft,
AutoWithManualInSameWord,
AutoWithoutManualInSameWord
};
struct HyphenationState {
uint32_t mostRecentBoundary = 0;
bool hasManualHyphen = false;
bool hasExplicitHyphen = false;
bool hasAutoHyphen = false;
};
/**
@ -344,6 +353,10 @@ public:
eSuppressAllBreaks
};
void ClassifyAutoHyphenations(uint32_t aStart, Range aRange,
nsTArray<HyphenType>& aHyphenBuffer,
HyphenationState* aWordState);
/**
* Finds the longest substring that will fit into the given width.
* Uses GetHyphenationBreaks and GetSpacing from aBreakProvider.

Просмотреть файл

@ -3182,7 +3182,6 @@ public:
JustificationInfo ComputeJustification(
Range aRange, nsTArray<JustificationAssignment>* aAssignments = nullptr);
const nsStyleText* StyleText() { return mTextStyle; }
nsTextFrame* GetFrame() { return mFrame; }
// This may not be equal to the frame offset/length in because we may have
// adjusted for whitespace trimming according to the state bits set in the frame
@ -3597,6 +3596,16 @@ PropertyProvider::GetHyphenWidth()
return mHyphenWidth + mLetterSpacing;
}
static inline bool
IS_HYPHEN(char16_t u)
{
return (u == char16_t('-') ||
u == 0x058A || // ARMENIAN HYPHEN
u == 0x2010 || // HYPHEN
u == 0x2012 || // FIGURE DASH
u == 0x2013); // EN DASH
}
void
PropertyProvider::GetHyphenationBreaks(Range aRange, HyphenType* aBreakBefore)
{
@ -3642,7 +3651,7 @@ PropertyProvider::GetHyphenationBreaks(Range aRange, HyphenType* aBreakBefore)
allowHyphenBreakBeforeNextChar &&
(!(mFrame->GetStateBits() & TEXT_START_OF_LINE) ||
run.GetSkippedOffset() > mStart.GetSkippedOffset())
? HyphenType::Manual
? HyphenType::Soft
: HyphenType::None;
allowHyphenBreakBeforeNextChar = false;
}
@ -3650,8 +3659,16 @@ PropertyProvider::GetHyphenationBreaks(Range aRange, HyphenType* aBreakBefore)
if (mTextStyle->mHyphens == StyleHyphens::Auto) {
for (uint32_t i = 0; i < aRange.Length(); ++i) {
if (mTextRun->CanHyphenateBefore(aRange.start + i)) {
aBreakBefore[i] = HyphenType::Auto;
int32_t fragIndex = mFrag->GetLength() > aRange.end ?
aRange.start + i : i;
if (IS_HYPHEN(mFrag->CharAt(fragIndex))) {
aBreakBefore[i] = HyphenType::Explicit;
continue;
}
if (mTextRun->CanHyphenateBefore(aRange.start + i) &&
aBreakBefore[i] == HyphenType::None) {
aBreakBefore[i] = HyphenType::AutoWithoutManualInSameWord;
}
}
}