Bug 380484. Work around ATSUI's 32k pixel limitation; when creating textruns, split the textrun up into smaller pieces if necessary before we pass it to ATSUI. r=vlad

This commit is contained in:
roc+@cs.cmu.edu 2007-05-14 19:54:19 -07:00
parent 3031ce0327
commit 77ac654c2d
2 changed files with 186 additions and 75 deletions

View File

@ -128,8 +128,11 @@ protected:
const nsACString& aGenericName,
void *closure);
void InitTextRun(gfxTextRun *aRun, const PRUnichar *aString, PRUint32 aLength,
PRBool aWrapped);
PRUint32 GuessMaximumStringLength();
/** Returns true for success */
PRBool InitTextRun(gfxTextRun *aRun, const PRUnichar *aString, PRUint32 aLength,
PRBool aWrapped, PRUint32 aSegmentStart, PRUint32 aSegmentLength);
ATSUFontFallbacks mFallbacks;
};

View File

@ -422,21 +422,6 @@ SetupClusterBoundaries(gfxTextRun *aTextRun, const PRUnichar *aString)
UCDisposeTextBreakLocator(&locator);
}
void
gfxAtsuiFontGroup::MakeTextRunInternal(const PRUnichar *aString, PRUint32 aLength,
PRBool aWrapped, gfxTextRun *aTextRun)
{
// NS_ASSERTION(!(aParams->mFlags & TEXT_NEED_BOUNDING_BOX),
// "Glyph extents not yet supported");
const PRUnichar *realString = aString + (aWrapped ? 1 : 0);
aTextRun->RecordSurrogates(realString);
if (!(aTextRun->GetFlags() & TEXT_IS_8BIT)) {
SetupClusterBoundaries(aTextRun, realString);
}
InitTextRun(aTextRun, aString, aLength, aWrapped);
}
#define UNICODE_LRO 0x202d
#define UNICODE_RLO 0x202e
#define UNICODE_PDF 0x202c
@ -448,6 +433,70 @@ AppendDirectionalIndicator(PRUint32 aFlags, nsAString& aString)
aString.Append(overrides[(aFlags & gfxTextRunFactory::TEXT_IS_RTL) != 0]);
}
/**
* Given a textrun and an offset into that textrun, we need to choose a length
* for the substring of the textrun that we should analyze next. The length
* should be <= aMaxLength if possible. It must always end at a cluster
* boundary and it should end at the end of the textrun or at the
* boundary of a space if possible.
*/
static PRUint32
FindTextRunSegmentLength(gfxTextRun *aTextRun, PRUint32 aOffset, PRUint32 aMaxLength)
{
if (aOffset + aMaxLength >= aTextRun->GetLength()) {
// The remaining part of the textrun fits within the max length,
// so just use it.
return aTextRun->GetLength() - aOffset;
}
// Try to end the segment before or after a space, since spaces don't kern
// or ligate.
PRUint32 end;
for (end = aOffset + aMaxLength; end > aOffset; --end) {
if (aTextRun->IsClusterStart(end) &&
(aTextRun->GetChar(end) == ' ' || aTextRun->GetChar(end - 1) == ' '))
return end - aOffset;
}
// Try to end the segment at the last cluster boundary.
for (end = aOffset + aMaxLength; end > aOffset; --end) {
if (aTextRun->IsClusterStart(end))
return end - aOffset;
}
// I guess we just have to return a segment that's the entire cluster
// starting at aOffset.
for (end = aOffset + 1; end < aTextRun->GetLength(); ++end) {
if (aTextRun->IsClusterStart(end))
return end - aOffset;
}
return aTextRun->GetLength() - aOffset;
}
PRUint32
gfxAtsuiFontGroup::GuessMaximumStringLength()
{
gfxFloat maxAdvance = GetFontAt(0)->GetMetrics().maxAdvance;
// ATSUI can't handle offsets of more than 32K pixels
PRUint32 chars = 0x7FFF/PRUint32(maxAdvance);
return PR_MAX(1, chars);
}
/*
* ATSUI can't handle more than 32K pixels of text. We can easily have
* textruns longer than that. Our strategy here is to divide the textrun up
* into pieces each of which is less than 32K pixels wide. We pick a number
* of characters 'maxLen' such that the first font's max-advance times that
* number of characters is less than 32K pixels; then we try glyph conversion
* of the string broken up into chunks each with no more than 'maxLen'
* characters. That could fail (e.g. if fallback fonts are used); if it does,
* we retry with a smaller maxLen. When breaking up the string into chunks
* we prefer to break at space boundaries because spaces don't kern or ligate
* with other characters, usually. We insist on breaking at cluster boundaries.
* If the font size is incredibly huge and/or clusters are very large, this
* could mean that we actually put more than 'maxLen' characters in a chunk.
*/
gfxTextRun *
gfxAtsuiFontGroup::MakeTextRun(const PRUnichar *aString, PRUint32 aLength,
const Parameters *aParams, PRUint32 aFlags)
@ -456,14 +505,33 @@ gfxAtsuiFontGroup::MakeTextRun(const PRUnichar *aString, PRUint32 aLength,
if (!textRun)
return nsnull;
textRun->RecordSurrogates(aString);
SetupClusterBoundaries(textRun, aString);
PRUint32 maxLen;
nsAutoString utf16;
AppendDirectionalIndicator(aFlags, utf16);
utf16.Append(aString, aLength);
// Ensure that none of the whitespace in the run is considered "trailing"
// by ATSUI's bidi algorithm
utf16.Append('.');
utf16.Append(UNICODE_PDF);
MakeTextRunInternal(utf16.get(), utf16.Length(), PR_TRUE, textRun);
for (maxLen = GuessMaximumStringLength(); maxLen > 0; maxLen /= 2) {
PRUint32 start = 0;
while (start < aLength) {
PRUint32 len = FindTextRunSegmentLength(textRun, start, maxLen);
utf16.Truncate();
AppendDirectionalIndicator(aFlags, utf16);
utf16.Append(aString + start, len);
// Ensure that none of the whitespace in the run is considered "trailing"
// by ATSUI's bidi algorithm
utf16.Append('.');
utf16.Append(UNICODE_PDF);
if (!InitTextRun(textRun, utf16.get(), utf16.Length(), PR_TRUE,
start, len) && maxLen > 1)
break;
start += len;
}
if (start == aLength)
break;
textRun->ResetGlyphRuns();
}
return textRun;
}
@ -476,19 +544,35 @@ gfxAtsuiFontGroup::MakeTextRun(const PRUint8 *aString, PRUint32 aLength,
if (!textRun)
return nsnull;
nsDependentCSubstring cString(reinterpret_cast<const char*>(aString),
reinterpret_cast<const char*>(aString + aLength));
PRUint32 maxLen;
nsAutoString utf16;
PRBool wrapBidi = (aFlags & TEXT_IS_RTL) != 0;
if (wrapBidi) {
AppendDirectionalIndicator(aFlags, utf16);
for (maxLen = GuessMaximumStringLength(); maxLen > 0; maxLen /= 2) {
PRUint32 start = 0;
while (start < aLength) {
PRUint32 len = FindTextRunSegmentLength(textRun, start, maxLen);
nsDependentCSubstring cString(reinterpret_cast<const char*>(aString + start),
reinterpret_cast<const char*>(aString + start + len));
utf16.Truncate();
PRBool wrapBidi = (aFlags & TEXT_IS_RTL) != 0;
if (wrapBidi) {
AppendDirectionalIndicator(aFlags, utf16);
}
AppendASCIItoUTF16(cString, utf16);
if (wrapBidi) {
utf16.Append('.');
utf16.Append(UNICODE_PDF);
}
if (!InitTextRun(textRun, utf16.get(), utf16.Length(), wrapBidi,
start, len) && maxLen > 1)
break;
start += len;
}
if (start == aLength)
break;
textRun->ResetGlyphRuns();
}
AppendASCIItoUTF16(cString, utf16);
if (wrapBidi) {
utf16.Append('.');
utf16.Append(UNICODE_PDF);
}
MakeTextRunInternal(utf16.get(), utf16.Length(), wrapBidi, textRun);
return textRun;
}
@ -540,7 +624,12 @@ private:
ATSUDirectDataSelector mSelector;
};
#define ATSUI_SPECIAL_GLYPH_ID 0xFFFF
#define ATSUI_SPECIAL_GLYPH_ID 0xFFFF
/**
* This flag seems to be set on glyphs that have overrun the 32K pixel
* limit in ATSUI.
*/
#define ATSUI_OVERRUNNING_GLYPH_FLAG 0x100000
/**
* Calculate the advance in appunits of a run of ATSUI glyphs
@ -561,7 +650,8 @@ GetAdvanceAppUnits(ATSLayoutRecord *aGlyphs, PRUint32 aGlyphCount,
static void
SetGlyphsForCharacterGroup(ATSLayoutRecord *aGlyphs, PRUint32 aGlyphCount,
Fixed *aBaselineDeltas, PRUint32 aAppUnitsPerDevUnit,
gfxTextRun *aRun, const PRPackedBool *aUnmatched,
gfxTextRun *aRun, PRUint32 aSegmentStart,
const PRPackedBool *aUnmatched,
const PRUnichar *aString)
{
NS_ASSERTION(aGlyphCount > 0, "Must set at least one glyph");
@ -593,7 +683,7 @@ SetGlyphsForCharacterGroup(ATSLayoutRecord *aGlyphs, PRUint32 aGlyphCount,
if (!allMatched) {
for (i = firstOffset; i <= lastOffset; ++i) {
PRUint32 index = i/2;
aRun->SetMissingGlyph(index, aString[index]);
aRun->SetMissingGlyph(aSegmentStart + index, aString[index]);
}
return;
}
@ -605,9 +695,9 @@ SetGlyphsForCharacterGroup(ATSLayoutRecord *aGlyphs, PRUint32 aGlyphCount,
if (!inOrder) {
// Because the characters in this group were not in the textrun's
// required order, we must make the entire group an indivisible cluster
aRun->SetCharacterGlyph(index, g.SetClusterContinuation());
aRun->SetCharacterGlyph(aSegmentStart + index, g.SetClusterContinuation());
} else if (!aRun->GetCharacterGlyphs()[index].IsClusterContinuation()) {
aRun->SetCharacterGlyph(index, g.SetLigatureContinuation());
aRun->SetCharacterGlyph(aSegmentStart + index, g.SetLigatureContinuation());
}
}
@ -619,7 +709,7 @@ SetGlyphsForCharacterGroup(ATSLayoutRecord *aGlyphs, PRUint32 aGlyphCount,
(!aBaselineDeltas || aBaselineDeltas[displayGlyph - aGlyphs] == 0) &&
gfxTextRun::CompressedGlyph::IsSimpleAdvance(advance) &&
gfxTextRun::CompressedGlyph::IsSimpleGlyphID(displayGlyph->glyphID)) {
aRun->SetCharacterGlyph(index, g.SetSimpleGlyph(advance, displayGlyph->glyphID));
aRun->SetCharacterGlyph(aSegmentStart + index, g.SetSimpleGlyph(advance, displayGlyph->glyphID));
return;
}
}
@ -647,20 +737,24 @@ SetGlyphsForCharacterGroup(ATSLayoutRecord *aGlyphs, PRUint32 aGlyphCount,
}
if (detailedGlyphs.Length() == 0) {
NS_WARNING("No glyphs visible at all!");
aRun->SetCharacterGlyph(index, g.SetMissing());
aRun->SetCharacterGlyph(aSegmentStart + index, g.SetMissing());
return;
}
detailedGlyphs[detailedGlyphs.Length() - 1].mIsLastGlyph = PR_TRUE;
detailedGlyphs[detailedGlyphs.Length() - 1].mAdvance =
GetAdvanceAppUnits(advanceStart, aGlyphs + aGlyphCount - advanceStart, aAppUnitsPerDevUnit);
aRun->SetDetailedGlyphs(index, detailedGlyphs.Elements(), detailedGlyphs.Length());
aRun->SetDetailedGlyphs(aSegmentStart + index, detailedGlyphs.Elements(), detailedGlyphs.Length());
}
static void
/**
* Returns true if there are overrunning glyphs
*/
static PRBool
PostLayoutCallback(ATSULineRef aLine, gfxTextRun *aRun,
const PRUnichar *aString, PRBool aWrapped,
const PRPackedBool *aUnmatched)
const PRPackedBool *aUnmatched,
PRUint32 aSegmentStart, PRUint32 aSegmentLength)
{
// AutoLayoutDataArrayPtr advanceDeltasArray(aLine, kATSUDirectDataAdvanceDeltaFixedArray);
// Fixed *advanceDeltas = NS_STATIC_CAST(Fixed *, advanceDeltasArray.mArray);
@ -672,7 +766,7 @@ PostLayoutCallback(ATSULineRef aLine, gfxTextRun *aRun,
PRUint32 numGlyphs = glyphRecordsArray.mItemCount;
if (numGlyphs == 0 || !glyphRecordsArray.mArray) {
NS_WARNING("Failed to retrieve key glyph data");
return;
return PR_FALSE;
}
ATSLayoutRecord *glyphRecords = NS_STATIC_CAST(ATSLayoutRecord *, glyphRecordsArray.mArray);
NS_ASSERTION(!baselineDeltas || baselineDeltasArray.mItemCount == numGlyphs,
@ -681,7 +775,7 @@ PostLayoutCallback(ATSULineRef aLine, gfxTextRun *aRun,
"Last glyph should be a terminator glyph");
--numGlyphs;
if (numGlyphs == 0)
return;
return PR_FALSE;
PRUint32 appUnitsPerDevUnit = aRun->GetAppUnitsPerDevUnit();
PRBool isRTL = aRun->IsRightToLeft();
@ -690,18 +784,19 @@ PostLayoutCallback(ATSULineRef aLine, gfxTextRun *aRun,
// The glyph array includes a glyph for the artificial trailing
// non-whitespace character. Strip that glyph from the array now.
if (isRTL) {
NS_ASSERTION(glyphRecords[0].originalOffset == aRun->GetLength()*2,
NS_ASSERTION(glyphRecords[0].originalOffset == aSegmentLength*2,
"Couldn't find glyph for trailing marker");
glyphRecords++;
} else {
NS_ASSERTION(glyphRecords[numGlyphs - 1].originalOffset == aRun->GetLength()*2,
NS_ASSERTION(glyphRecords[numGlyphs - 1].originalOffset == aSegmentLength*2,
"Couldn't find glyph for trailing marker");
}
--numGlyphs;
if (numGlyphs == 0)
return;
return PR_FALSE;
}
PRUint32 allFlags = 0;
// Now process the glyphs, which should basically be in
// the textrun's desired order, so process them in textrun order
PRInt32 direction = PRInt32(aRun->GetDirection());
@ -713,6 +808,7 @@ PostLayoutCallback(ATSULineRef aLine, gfxTextRun *aRun,
while (glyphCount < numGlyphs) {
ATSLayoutRecord *glyph = &glyphRecords[glyphIndex + direction*glyphCount];
PRUint32 glyphOffset = glyph->originalOffset;
allFlags |= glyph->flags;
// Always add the current glyph to the group if it's for the same
// character as a character whose glyph is already in the group,
// or an earlier character. The latter can happen because ATSUI
@ -722,7 +818,7 @@ PostLayoutCallback(ATSULineRef aLine, gfxTextRun *aRun,
// In this case we need to make sure the glyph for the consonant
// is added to the group containing the vowel.
if (lastOffset < glyphOffset) {
if (!aRun->IsClusterStart(glyphOffset/2)) {
if (!aRun->IsClusterStart(aSegmentStart + glyphOffset/2)) {
// next character is a cluster continuation,
// add it to the current group
lastOffset = glyphOffset;
@ -747,13 +843,13 @@ PostLayoutCallback(ATSULineRef aLine, gfxTextRun *aRun,
SetGlyphsForCharacterGroup(glyphRecords + numGlyphs - glyphCount,
glyphCount,
baselineDeltas ? baselineDeltas + numGlyphs - glyphCount : nsnull,
appUnitsPerDevUnit, aRun, aUnmatched,
aString);
appUnitsPerDevUnit, aRun, aSegmentStart,
aUnmatched, aString);
} else {
SetGlyphsForCharacterGroup(glyphRecords,
glyphCount, baselineDeltas,
appUnitsPerDevUnit, aRun, aUnmatched,
aString);
appUnitsPerDevUnit, aRun, aSegmentStart,
aUnmatched, aString);
glyphRecords += glyphCount;
if (baselineDeltas) {
baselineDeltas += glyphCount;
@ -761,17 +857,23 @@ PostLayoutCallback(ATSULineRef aLine, gfxTextRun *aRun,
}
numGlyphs -= glyphCount;
}
return (allFlags & ATSUI_OVERRUNNING_GLYPH_FLAG) != 0;
}
struct PostLayoutCallbackClosure {
gfxTextRun *mTextRun;
const PRUnichar *mString;
// This is true when we inserted an artifical trailing character at the
// end of the string when computing the ATSUI layout.
PRPackedBool mWrapped;
// Either null or an array of stringlength booleans set to true for
// each character that did not match any fonts
nsAutoArrayPtr<PRPackedBool> mUnmatchedChars;
PRUint32 mSegmentStart;
PRUint32 mSegmentLength;
// This is true when we inserted an artifical trailing character at the
// end of the string when computing the ATSUI layout.
PRPackedBool mWrapped;
// The callback *sets* this to indicate whether there were overrunning glyphs
PRPackedBool mOverrunningGlyphs;
};
// This is really disgusting, but the ATSUI refCon thing is also disgusting
@ -784,17 +886,21 @@ PostLayoutOperationCallback(ATSULayoutOperationSelector iCurrentOperation,
void *iOperationCallbackParameterPtr,
ATSULayoutOperationCallbackStatus *oCallbackStatus)
{
PostLayoutCallback(iLineRef, gCallbackClosure->mTextRun,
gCallbackClosure->mString, gCallbackClosure->mWrapped,
gCallbackClosure->mUnmatchedChars);
gCallbackClosure->mOverrunningGlyphs =
PostLayoutCallback(iLineRef, gCallbackClosure->mTextRun,
gCallbackClosure->mString, gCallbackClosure->mWrapped,
gCallbackClosure->mUnmatchedChars,
gCallbackClosure->mSegmentStart,
gCallbackClosure->mSegmentLength);
*oCallbackStatus = kATSULayoutOperationCallbackStatusContinue;
return noErr;
}
void
PRBool
gfxAtsuiFontGroup::InitTextRun(gfxTextRun *aRun,
const PRUnichar *aString, PRUint32 aLength,
PRBool aWrapped)
PRBool aWrapped, PRUint32 aSegmentStart,
PRUint32 aSegmentLength)
{
OSStatus status;
gfxAtsuiFont *atsuiFont = GetFontAt(0);
@ -802,17 +908,16 @@ gfxAtsuiFontGroup::InitTextRun(gfxTextRun *aRun,
nsTArray<ATSUStyle> stylesToDispose;
PRUint32 headerChars = aWrapped ? 1 : 0;
const PRUnichar *realString = aString + headerChars;
PRUint32 realLength = aRun->GetLength();
NS_ASSERTION(realLength == aLength - (aWrapped ? 3 : 0),
NS_ASSERTION(aSegmentLength == aLength - (aWrapped ? 3 : 0),
"Length mismatch");
#ifdef DUMP_TEXT_RUNS
NS_ConvertUTF16toUTF8 str(realString, realLength);
NS_ConvertUTF16toUTF8 str(realString, aSegmentLength);
NS_ConvertUTF16toUTF8 families(mFamilies);
printf("%p(%s) TEXTRUN \"%s\" ENDTEXTRUN\n", this, families.get(), str.get());
#endif
UniCharCount runLengths = realLength;
UniCharCount runLengths = aSegmentLength;
ATSUTextLayout layout;
// Create the text layout for the whole string, but only produce glyphs
// for the text inside LRO/RLO - PDF, if present. For wrapped strings
@ -821,7 +926,7 @@ gfxAtsuiFontGroup::InitTextRun(gfxTextRun *aRun,
status = ATSUCreateTextLayoutWithTextPtr
(aString,
headerChars,
realLength + (aWrapped ? 1 : 0),
aSegmentLength + (aWrapped ? 1 : 0),
aLength,
1,
&runLengths,
@ -833,6 +938,8 @@ gfxAtsuiFontGroup::InitTextRun(gfxTextRun *aRun,
closure.mTextRun = aRun;
closure.mString = realString;
closure.mWrapped = aWrapped;
closure.mSegmentStart = aSegmentStart;
closure.mSegmentLength = aSegmentLength;
NS_ASSERTION(!gCallbackClosure, "Reentering InitTextRun? Expect disaster!");
gCallbackClosure = &closure;
@ -868,8 +975,8 @@ gfxAtsuiFontGroup::InitTextRun(gfxTextRun *aRun,
/* Now go through and update the styles for the text, based on font matching. */
UniCharArrayOffset runStart = headerChars;
UniCharCount totalLength = runStart + realLength;
UniCharCount runLength = realLength;
UniCharCount totalLength = runStart + aSegmentLength;
UniCharCount runLength = aSegmentLength;
//fprintf (stderr, "==== Starting font maching [string length: %d]\n", totalLength);
while (runStart < totalLength) {
@ -882,7 +989,7 @@ gfxAtsuiFontGroup::InitTextRun(gfxTextRun *aRun,
if (status == noErr) {
//fprintf (stderr, "ATSUMatchFontsToText returned noErr\n");
// everything's good, finish up
aRun->AddGlyphRun(atsuiFont, runStart - headerChars);
aRun->AddGlyphRun(atsuiFont, aSegmentStart + runStart - headerChars);
break;
} else if (status == kATSUFontsMatched) {
//fprintf (stderr, "ATSUMatchFontsToText returned kATSUFontsMatched: FID %d\n", substituteFontID);
@ -898,14 +1005,14 @@ gfxAtsuiFontGroup::InitTextRun(gfxTextRun *aRun,
ATSUSetAttributes (subStyle, 1, fontTags, fontArgSizes, fontArgs);
if (changedOffset > runStart) {
aRun->AddGlyphRun(atsuiFont, runStart - headerChars);
aRun->AddGlyphRun(atsuiFont, aSegmentStart + runStart - headerChars);
}
ATSUSetRunStyle (layout, subStyle, changedOffset, changedLength);
gfxAtsuiFont *font = FindFontFor(substituteFontID);
if (font) {
aRun->AddGlyphRun(font, changedOffset - headerChars);
aRun->AddGlyphRun(font, aSegmentStart + changedOffset - headerChars);
}
stylesToDispose.AppendElement(subStyle);
@ -913,7 +1020,7 @@ gfxAtsuiFontGroup::InitTextRun(gfxTextRun *aRun,
//fprintf (stderr, "ATSUMatchFontsToText returned kATSUFontsNotMatched\n");
/* I need to select the last resort font; how the heck do I do that? */
// Record which font is associated with these glyphs, anyway
aRun->AddGlyphRun(atsuiFont, runStart - headerChars);
aRun->AddGlyphRun(atsuiFont, aSegmentStart + runStart - headerChars);
if (!closure.mUnmatchedChars) {
closure.mUnmatchedChars = new PRPackedBool[aLength];
@ -937,7 +1044,7 @@ gfxAtsuiFontGroup::InitTextRun(gfxTextRun *aRun,
// the result of this call.
ATSTrapezoid trap;
ItemCount trapCount;
ATSUGetGlyphBounds(layout, 0, 0, headerChars, realLength,
ATSUGetGlyphBounds(layout, 0, 0, headerChars, aSegmentLength,
kATSUseFractionalOrigins, 1, &trap, &trapCount);
ATSUDisposeTextLayout(layout);
@ -948,4 +1055,5 @@ gfxAtsuiFontGroup::InitTextRun(gfxTextRun *aRun,
ATSUDisposeStyle(stylesToDispose[i]);
}
gCallbackClosure = nsnull;
return !closure.mOverrunningGlyphs;
}