зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1056516 - let auto hyphen honor manual hyphen when hyphens:auto is set. r=jfkthame
MozReview-Commit-ID: F3ALAJRHned
This commit is contained in:
Родитель
2e09040e6e
Коммит
0316fede8c
|
@ -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,9 +1061,36 @@ 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) {
|
||||
charAdvance = GetAdvanceForGlyphs(Range(i, i + 1));
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче