Bug 375662. Fix ATSUI issues with cluster detection. Also fix extraction of glyphs when trailing whitespace doesn't get the dominant text direction; remove the current workaround and replace it with a simpler workaround. r=vlad

This commit is contained in:
roc+@cs.cmu.edu 2007-03-28 13:53:46 -07:00
Родитель 59edcd91ad
Коммит 5bb822a577
2 изменённых файлов: 91 добавлений и 93 удалений

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

@ -101,11 +101,14 @@ public:
Parameters* aParams); Parameters* aParams);
virtual gfxTextRun *MakeTextRun(const PRUint8* aString, PRUint32 aLength, virtual gfxTextRun *MakeTextRun(const PRUint8* aString, PRUint32 aLength,
Parameters* aParams); Parameters* aParams);
// Here, aString is actually aLength + aHeaderChars*2 chars long; the first char // When aWrapped is true, the string includes bidi control
// may be a LRO or RLO bidi control character to force setting the direction // characters. The first character will be LRO or LRO to force setting the
// for all characters, and if so the last character will be a PDF // direction for all characters, the last character is PDF, and the
// second to last character is a non-whitespace character --- to ensure
// that there is no "trailing whitespace" in the string, see
// http://weblogs.mozillazine.org/roc/archives/2007/02/superlaser_targ.html#comments
gfxTextRun *MakeTextRunInternal(const PRUnichar *aString, PRUint32 aLength, gfxTextRun *MakeTextRunInternal(const PRUnichar *aString, PRUint32 aLength,
Parameters *aParams, PRUint32 aheaderChars); PRBool aWrapped, Parameters *aParams);
ATSUFontFallbacks *GetATSUFontFallbacksPtr() { return &mFallbacks; } ATSUFontFallbacks *GetATSUFontFallbacksPtr() { return &mFallbacks; }
@ -121,7 +124,7 @@ protected:
void *closure); void *closure);
void InitTextRun(gfxTextRun *aRun, const PRUnichar *aString, PRUint32 aLength, void InitTextRun(gfxTextRun *aRun, const PRUnichar *aString, PRUint32 aLength,
PRUint32 aHeaderChars); PRBool aWrapped);
ATSUFontFallbacks mFallbacks; ATSUFontFallbacks mFallbacks;
}; };

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

@ -349,7 +349,7 @@ gfxAtsuiFontGroup::Copy(const gfxFontStyle *aStyle)
} }
static void static void
SetupClusterBoundaries(gfxTextRun *aTextRun, const PRUnichar *aString, PRUint32 aLength) SetupClusterBoundaries(gfxTextRun *aTextRun, const PRUnichar *aString)
{ {
TextBreakLocatorRef locator; TextBreakLocatorRef locator;
OSStatus status = UCCreateTextBreakLocator(NULL, 0, kUCTextBreakClusterMask, OSStatus status = UCCreateTextBreakLocator(NULL, 0, kUCTextBreakClusterMask,
@ -357,50 +357,51 @@ SetupClusterBoundaries(gfxTextRun *aTextRun, const PRUnichar *aString, PRUint32
if (status != noErr) if (status != noErr)
return; return;
UniCharArrayOffset breakOffset; UniCharArrayOffset breakOffset;
status = UCFindTextBreak(locator, kUCTextBreakClusterMask, 0, aString, aLength, PRUint32 length = aTextRun->GetLength();
0, &breakOffset); status = UCFindTextBreak(locator, kUCTextBreakClusterMask,
kUCTextBreakLeadingEdgeMask, aString, length, 0, &breakOffset);
if (status != noErr) { if (status != noErr) {
UCDisposeTextBreakLocator(&locator); UCDisposeTextBreakLocator(&locator);
return; return;
} }
NS_ASSERTION(breakOffset == 0, "Cluster should start at offset zero");
gfxTextRun::CompressedGlyph g; gfxTextRun::CompressedGlyph g;
while (breakOffset < aLength) { PRUint32 lastBreak = 1;
PRUint32 curOffset = breakOffset; do {
while (lastBreak < breakOffset) {
aTextRun->SetCharacterGlyph(lastBreak, g.SetClusterContinuation());
++lastBreak;
}
status = UCFindTextBreak(locator, kUCTextBreakClusterMask, status = UCFindTextBreak(locator, kUCTextBreakClusterMask,
kUCTextBreakIterateMask, kUCTextBreakIterateMask|kUCTextBreakLeadingEdgeMask,
aString, aLength, curOffset, &breakOffset); aString, length, breakOffset, &breakOffset);
if (status != noErr) { if (status != noErr) {
UCDisposeTextBreakLocator(&locator); UCDisposeTextBreakLocator(&locator);
return; return;
} }
PRUint32 j; ++lastBreak;
for (j = curOffset + 1; j < breakOffset; ++j) { } while (breakOffset < length);
aTextRun->SetCharacterGlyph(j, g.SetClusterContinuation()); NS_ASSERTION(breakOffset == length, "Should have found a final break");
}
}
NS_ASSERTION(breakOffset == aLength, "Should have found a final break");
UCDisposeTextBreakLocator(&locator); UCDisposeTextBreakLocator(&locator);
} }
gfxTextRun * gfxTextRun *
gfxAtsuiFontGroup::MakeTextRunInternal(const PRUnichar *aString, PRUint32 aLength, gfxAtsuiFontGroup::MakeTextRunInternal(const PRUnichar *aString, PRUint32 aLength,
Parameters *aParams, PRUint32 aHeaderChars) PRBool aWrapped, Parameters *aParams)
{ {
// NS_ASSERTION(!(aParams->mFlags & TEXT_NEED_BOUNDING_BOX), // NS_ASSERTION(!(aParams->mFlags & TEXT_NEED_BOUNDING_BOX),
// "Glyph extents not yet supported"); // "Glyph extents not yet supported");
gfxTextRun *textRun = new gfxTextRun(aParams, aLength); gfxTextRun *textRun = new gfxTextRun(aParams, aLength - (aWrapped ? 3 : 0));
if (!textRun) if (!textRun)
return nsnull; return nsnull;
// There's a one-char header in the string and a one-char trailer const PRUnichar *realString = aString + (aWrapped ? 1 : 0);
textRun->RecordSurrogates(aString + aHeaderChars); textRun->RecordSurrogates(realString);
if (!(aParams->mFlags & TEXT_IS_8BIT)) { if (!(aParams->mFlags & TEXT_IS_8BIT)) {
SetupClusterBoundaries(textRun, aString + aHeaderChars, aLength); SetupClusterBoundaries(textRun, realString);
} }
InitTextRun(textRun, aString, aLength, aHeaderChars); InitTextRun(textRun, aString, aLength, aWrapped);
return textRun; return textRun;
} }
@ -422,8 +423,11 @@ gfxAtsuiFontGroup::MakeTextRun(const PRUnichar *aString, PRUint32 aLength,
nsAutoString utf16; nsAutoString utf16;
AppendDirectionalIndicator(aParams->mFlags, utf16); AppendDirectionalIndicator(aParams->mFlags, utf16);
utf16.Append(aString, aLength); 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); utf16.Append(UNICODE_PDF);
return MakeTextRunInternal(utf16.get(), aLength, aParams, 1); return MakeTextRunInternal(utf16.get(), utf16.Length(), PR_TRUE, aParams);
} }
gfxTextRun * gfxTextRun *
@ -434,16 +438,16 @@ gfxAtsuiFontGroup::MakeTextRun(const PRUint8 *aString, PRUint32 aLength,
nsDependentCSubstring cString(reinterpret_cast<const char*>(aString), nsDependentCSubstring cString(reinterpret_cast<const char*>(aString),
reinterpret_cast<const char*>(aString + aLength)); reinterpret_cast<const char*>(aString + aLength));
nsAutoString utf16; nsAutoString utf16;
PRUint32 headerChars = 0; PRBool wrapBidi = (aParams->mFlags & TEXT_IS_RTL) != 0;
if (aParams->mFlags & TEXT_IS_RTL) { if (wrapBidi) {
AppendDirectionalIndicator(aParams->mFlags, utf16); AppendDirectionalIndicator(aParams->mFlags, utf16);
headerChars = 1;
} }
AppendASCIItoUTF16(cString, utf16); AppendASCIItoUTF16(cString, utf16);
if (aParams->mFlags & TEXT_IS_RTL) { if (wrapBidi) {
utf16.Append('.');
utf16.Append(UNICODE_PDF); utf16.Append(UNICODE_PDF);
} }
return MakeTextRunInternal(utf16.get(), aLength, aParams, headerChars); return MakeTextRunInternal(utf16.get(), utf16.Length(), wrapBidi, aParams);
} }
gfxAtsuiFont* gfxAtsuiFont*
@ -616,7 +620,8 @@ SetGlyphsForCharacterGroup(ATSLayoutRecord *aGlyphs, PRUint32 aGlyphCount,
static void static void
PostLayoutCallback(ATSULineRef aLine, gfxTextRun *aRun, PostLayoutCallback(ATSULineRef aLine, gfxTextRun *aRun,
const PRUnichar *aString, const PRPackedBool *aUnmatched) const PRUnichar *aString, PRBool aWrapped,
const PRPackedBool *aUnmatched)
{ {
// AutoLayoutDataArrayPtr advanceDeltasArray(aLine, kATSUDirectDataAdvanceDeltaFixedArray); // AutoLayoutDataArrayPtr advanceDeltasArray(aLine, kATSUDirectDataAdvanceDeltaFixedArray);
// Fixed *advanceDeltas = NS_STATIC_CAST(Fixed *, advanceDeltasArray.mArray); // Fixed *advanceDeltas = NS_STATIC_CAST(Fixed *, advanceDeltasArray.mArray);
@ -640,50 +645,25 @@ PostLayoutCallback(ATSULineRef aLine, gfxTextRun *aRun,
return; return;
PRUint32 appUnitsPerDevUnit = aRun->GetAppUnitsPerDevUnit(); PRUint32 appUnitsPerDevUnit = aRun->GetAppUnitsPerDevUnit();
// ATSUI seems to have a bug where trailing whitespace in a run,
// even after we've forced the direction with LRO/RLO/PDF, does not
// necessarily get the required direction.
// A specific testcase is "RLO space space PDF"; we get
// glyphRecords[0] = { originalOffset:0, realPos:0; }
// glyphRecords[1] = { originalOffset:2, realPos:>0 }
// We count the number of glyphs that appear to be this sort of erroneous
// whitespace.
// In RTL situations, the bug manifests as the trailing whitespace characters
// being rendered in LTR order at the end of the glyph array.
// In LTR situations, the bug manifests as the trailing whitespace characters
// being rendered in RTL order at the start of the glyph array.
// Compensate for this bug now by detecting those characters, setting up
// the glyphs for those characters, and then chopping those glyphs off
// the glyph array we need to look at.
PRUint32 stringTailOffset = aRun->GetLength() - 1;
PRBool isRTL = aRun->IsRightToLeft(); PRBool isRTL = aRun->IsRightToLeft();
if (isRTL) {
while (numGlyphs > 0 && if (aWrapped) {
glyphRecords[numGlyphs - 1].originalOffset == stringTailOffset*2 && // The glyph array includes a glyph for the artificial trailing
aString[stringTailOffset] == ' ') { // non-whitespace character. Strip that glyph from the array now.
SetGlyphsForCharacterGroup(glyphRecords + numGlyphs - 1, 1, if (isRTL) {
baselineDeltas ? baselineDeltas + numGlyphs - 1 : nsnull, NS_ASSERTION(glyphRecords[0].originalOffset == aRun->GetLength()*2,
appUnitsPerDevUnit, aRun, aUnmatched, "Couldn't find glyph for trailing marker");
aString); glyphRecords++;
--stringTailOffset; } else {
--numGlyphs; NS_ASSERTION(glyphRecords[numGlyphs - 1].originalOffset == aRun->GetLength()*2,
} "Couldn't find glyph for trailing marker");
} else {
while (numGlyphs > 0 &&
glyphRecords[0].originalOffset == stringTailOffset*2 &&
aString[stringTailOffset] == ' ') {
SetGlyphsForCharacterGroup(glyphRecords, 1,
baselineDeltas,
appUnitsPerDevUnit, aRun, aUnmatched,
aString);
--stringTailOffset;
--numGlyphs;
++glyphRecords;
} }
--numGlyphs;
if (numGlyphs == 0)
return;
} }
// Now process the rest of the glyphs, which should basically be in // Now process the glyphs, which should basically be in
// the textrun's desired order, so process them in textrun order // the textrun's desired order, so process them in textrun order
PRInt32 direction = PRInt32(aRun->GetDirection()); PRInt32 direction = PRInt32(aRun->GetDirection());
while (numGlyphs > 0) { while (numGlyphs > 0) {
@ -703,6 +683,12 @@ PostLayoutCallback(ATSULineRef aLine, gfxTextRun *aRun,
// In this case we need to make sure the glyph for the consonant // In this case we need to make sure the glyph for the consonant
// is added to the group containing the vowel. // is added to the group containing the vowel.
if (lastOffset < glyphOffset) { if (lastOffset < glyphOffset) {
if (!aRun->IsClusterStart(glyphOffset/2)) {
// next character is a cluster continuation,
// add it to the current group
lastOffset = glyphOffset;
continue;
}
// We could be at the end of a character group // We could be at the end of a character group
if (glyph->glyphID != ATSUI_SPECIAL_GLYPH_ID) { if (glyph->glyphID != ATSUI_SPECIAL_GLYPH_ID) {
// Next character is a normal character, stop the group here // Next character is a normal character, stop the group here
@ -741,6 +727,9 @@ PostLayoutCallback(ATSULineRef aLine, gfxTextRun *aRun,
struct PostLayoutCallbackClosure { struct PostLayoutCallbackClosure {
gfxTextRun *mTextRun; gfxTextRun *mTextRun;
const PRUnichar *mString; 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 // Either null or an array of stringlength booleans set to true for
// each character that did not match any fonts // each character that did not match any fonts
nsAutoArrayPtr<PRPackedBool> mUnmatchedChars; nsAutoArrayPtr<PRPackedBool> mUnmatchedChars;
@ -757,7 +746,8 @@ PostLayoutOperationCallback(ATSULayoutOperationSelector iCurrentOperation,
ATSULayoutOperationCallbackStatus *oCallbackStatus) ATSULayoutOperationCallbackStatus *oCallbackStatus)
{ {
PostLayoutCallback(iLineRef, gCallbackClosure->mTextRun, PostLayoutCallback(iLineRef, gCallbackClosure->mTextRun,
gCallbackClosure->mString, gCallbackClosure->mUnmatchedChars); gCallbackClosure->mString, gCallbackClosure->mWrapped,
gCallbackClosure->mUnmatchedChars);
*oCallbackStatus = kATSULayoutOperationCallbackStatusContinue; *oCallbackStatus = kATSULayoutOperationCallbackStatusContinue;
return noErr; return noErr;
} }
@ -765,30 +755,35 @@ PostLayoutOperationCallback(ATSULayoutOperationSelector iCurrentOperation,
void void
gfxAtsuiFontGroup::InitTextRun(gfxTextRun *aRun, gfxAtsuiFontGroup::InitTextRun(gfxTextRun *aRun,
const PRUnichar *aString, PRUint32 aLength, const PRUnichar *aString, PRUint32 aLength,
PRUint32 aHeaderChars) PRBool aWrapped)
{ {
OSStatus status; OSStatus status;
gfxAtsuiFont *atsuiFont = GetFontAt(0); gfxAtsuiFont *atsuiFont = GetFontAt(0);
ATSUStyle mainStyle = atsuiFont->GetATSUStyle(); ATSUStyle mainStyle = atsuiFont->GetATSUStyle();
nsTArray<ATSUStyle> stylesToDispose; nsTArray<ATSUStyle> stylesToDispose;
PRUint32 headerChars = aWrapped ? 1 : 0;
const PRUnichar *realString = aString + headerChars;
PRUint32 realLength = aRun->GetLength();
NS_ASSERTION(realLength == aLength - (aWrapped ? 3 : 0),
"Length mismatch");
#ifdef DUMP_TEXT_RUNS #ifdef DUMP_TEXT_RUNS
NS_ConvertUTF16toUTF8 str(aString + 1, aLength); NS_ConvertUTF16toUTF8 str(realString, realLength);
NS_ConvertUTF16toUTF8 families(mFamilies); NS_ConvertUTF16toUTF8 families(mFamilies);
printf("%p(%s) TEXTRUN \"%s\" ENDTEXTRUN\n", this, families.get(), str.get()); printf("%p(%s) TEXTRUN \"%s\" ENDTEXTRUN\n", this, families.get(), str.get());
#endif #endif
UniCharCount runLengths = aLength; UniCharCount runLengths = realLength;
ATSUTextLayout layout; ATSUTextLayout layout;
// The string is actually aLength + 2*aHeaderChars chars, with optionally // Create the text layout for the whole string, but only produce glyphs
// a header char to set the direction and a trailer char to pop it. So // for the text inside LRO/RLO - PDF, if present. For wrapped strings
// create the text layout giving the whole string as context, although we // we do need to produce glyphs for the trailing non-whitespace
// only want glyphs for the inner substring. // character to ensure that ATSUI treats all whitespace as non-trailing.
status = ATSUCreateTextLayoutWithTextPtr status = ATSUCreateTextLayoutWithTextPtr
(aString, (aString,
aHeaderChars, headerChars,
realLength + (aWrapped ? 1 : 0),
aLength, aLength,
aLength + aHeaderChars*2,
1, 1,
&runLengths, &runLengths,
&mainStyle, &mainStyle,
@ -797,8 +792,8 @@ gfxAtsuiFontGroup::InitTextRun(gfxTextRun *aRun,
PostLayoutCallbackClosure closure; PostLayoutCallbackClosure closure;
closure.mTextRun = aRun; closure.mTextRun = aRun;
// Pass the real string to the closure, ignoring the header closure.mString = realString;
closure.mString = aString + aHeaderChars; closure.mWrapped = aWrapped;
NS_ASSERTION(!gCallbackClosure, "Reentering InitTextRun? Expect disaster!"); NS_ASSERTION(!gCallbackClosure, "Reentering InitTextRun? Expect disaster!");
gCallbackClosure = &closure; gCallbackClosure = &closure;
@ -833,9 +828,9 @@ gfxAtsuiFontGroup::InitTextRun(gfxTextRun *aRun,
/* Now go through and update the styles for the text, based on font matching. */ /* Now go through and update the styles for the text, based on font matching. */
UniCharArrayOffset runStart = aHeaderChars; UniCharArrayOffset runStart = headerChars;
UniCharCount totalLength = aLength + aHeaderChars; UniCharCount totalLength = runStart + realLength;
UniCharCount runLength = aLength; UniCharCount runLength = realLength;
//fprintf (stderr, "==== Starting font maching [string length: %d]\n", totalLength); //fprintf (stderr, "==== Starting font maching [string length: %d]\n", totalLength);
while (runStart < totalLength) { while (runStart < totalLength) {
@ -848,7 +843,7 @@ gfxAtsuiFontGroup::InitTextRun(gfxTextRun *aRun,
if (status == noErr) { if (status == noErr) {
//fprintf (stderr, "ATSUMatchFontsToText returned noErr\n"); //fprintf (stderr, "ATSUMatchFontsToText returned noErr\n");
// everything's good, finish up // everything's good, finish up
aRun->AddGlyphRun(atsuiFont, runStart - aHeaderChars); aRun->AddGlyphRun(atsuiFont, runStart - headerChars);
break; break;
} else if (status == kATSUFontsMatched) { } else if (status == kATSUFontsMatched) {
//fprintf (stderr, "ATSUMatchFontsToText returned kATSUFontsMatched: FID %d\n", substituteFontID); //fprintf (stderr, "ATSUMatchFontsToText returned kATSUFontsMatched: FID %d\n", substituteFontID);
@ -864,14 +859,14 @@ gfxAtsuiFontGroup::InitTextRun(gfxTextRun *aRun,
ATSUSetAttributes (subStyle, 1, fontTags, fontArgSizes, fontArgs); ATSUSetAttributes (subStyle, 1, fontTags, fontArgSizes, fontArgs);
if (changedOffset > runStart) { if (changedOffset > runStart) {
aRun->AddGlyphRun(atsuiFont, runStart - aHeaderChars); aRun->AddGlyphRun(atsuiFont, runStart - headerChars);
} }
ATSUSetRunStyle (layout, subStyle, changedOffset, changedLength); ATSUSetRunStyle (layout, subStyle, changedOffset, changedLength);
gfxAtsuiFont *font = FindFontFor(substituteFontID); gfxAtsuiFont *font = FindFontFor(substituteFontID);
if (font) { if (font) {
aRun->AddGlyphRun(font, changedOffset - aHeaderChars); aRun->AddGlyphRun(font, changedOffset - headerChars);
} }
stylesToDispose.AppendElement(subStyle); stylesToDispose.AppendElement(subStyle);
@ -879,7 +874,7 @@ gfxAtsuiFontGroup::InitTextRun(gfxTextRun *aRun,
//fprintf (stderr, "ATSUMatchFontsToText returned kATSUFontsNotMatched\n"); //fprintf (stderr, "ATSUMatchFontsToText returned kATSUFontsNotMatched\n");
/* I need to select the last resort font; how the heck do I do that? */ /* I need to select the last resort font; how the heck do I do that? */
// Record which font is associated with these glyphs, anyway // Record which font is associated with these glyphs, anyway
aRun->AddGlyphRun(atsuiFont, runStart - aHeaderChars); aRun->AddGlyphRun(atsuiFont, runStart - headerChars);
if (!closure.mUnmatchedChars) { if (!closure.mUnmatchedChars) {
closure.mUnmatchedChars = new PRPackedBool[aLength]; closure.mUnmatchedChars = new PRPackedBool[aLength];
@ -888,7 +883,7 @@ gfxAtsuiFontGroup::InitTextRun(gfxTextRun *aRun,
} }
} }
if (closure.mUnmatchedChars) { if (closure.mUnmatchedChars) {
memset(closure.mUnmatchedChars.get() + changedOffset - aHeaderChars, memset(closure.mUnmatchedChars.get() + changedOffset - headerChars,
PR_TRUE, changedLength); PR_TRUE, changedLength);
} }
} }
@ -903,7 +898,7 @@ gfxAtsuiFontGroup::InitTextRun(gfxTextRun *aRun,
// the result of this call. // the result of this call.
ATSTrapezoid trap; ATSTrapezoid trap;
ItemCount trapCount; ItemCount trapCount;
ATSUGetGlyphBounds(layout, 0, 0, aHeaderChars, aLength, ATSUGetGlyphBounds(layout, 0, 0, headerChars, realLength,
kATSUseFractionalOrigins, 1, &trap, &trapCount); kATSUseFractionalOrigins, 1, &trap, &trapCount);
ATSUDisposeTextLayout(layout); ATSUDisposeTextLayout(layout);