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
parent 6c3ab3eb67
commit 7b0d468111
2 changed files with 91 additions and 93 deletions

View File

@ -101,11 +101,14 @@ public:
Parameters* aParams);
virtual gfxTextRun *MakeTextRun(const PRUint8* aString, PRUint32 aLength,
Parameters* aParams);
// Here, aString is actually aLength + aHeaderChars*2 chars long; the first char
// may be a LRO or RLO bidi control character to force setting the direction
// for all characters, and if so the last character will be a PDF
// When aWrapped is true, the string includes bidi control
// characters. The first character will be LRO or LRO to force setting the
// 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,
Parameters *aParams, PRUint32 aheaderChars);
PRBool aWrapped, Parameters *aParams);
ATSUFontFallbacks *GetATSUFontFallbacksPtr() { return &mFallbacks; }
@ -121,7 +124,7 @@ protected:
void *closure);
void InitTextRun(gfxTextRun *aRun, const PRUnichar *aString, PRUint32 aLength,
PRUint32 aHeaderChars);
PRBool aWrapped);
ATSUFontFallbacks mFallbacks;
};

View File

@ -349,7 +349,7 @@ gfxAtsuiFontGroup::Copy(const gfxFontStyle *aStyle)
}
static void
SetupClusterBoundaries(gfxTextRun *aTextRun, const PRUnichar *aString, PRUint32 aLength)
SetupClusterBoundaries(gfxTextRun *aTextRun, const PRUnichar *aString)
{
TextBreakLocatorRef locator;
OSStatus status = UCCreateTextBreakLocator(NULL, 0, kUCTextBreakClusterMask,
@ -357,50 +357,51 @@ SetupClusterBoundaries(gfxTextRun *aTextRun, const PRUnichar *aString, PRUint32
if (status != noErr)
return;
UniCharArrayOffset breakOffset;
status = UCFindTextBreak(locator, kUCTextBreakClusterMask, 0, aString, aLength,
0, &breakOffset);
PRUint32 length = aTextRun->GetLength();
status = UCFindTextBreak(locator, kUCTextBreakClusterMask,
kUCTextBreakLeadingEdgeMask, aString, length, 0, &breakOffset);
if (status != noErr) {
UCDisposeTextBreakLocator(&locator);
return;
}
NS_ASSERTION(breakOffset == 0, "Cluster should start at offset zero");
gfxTextRun::CompressedGlyph g;
while (breakOffset < aLength) {
PRUint32 curOffset = breakOffset;
PRUint32 lastBreak = 1;
do {
while (lastBreak < breakOffset) {
aTextRun->SetCharacterGlyph(lastBreak, g.SetClusterContinuation());
++lastBreak;
}
status = UCFindTextBreak(locator, kUCTextBreakClusterMask,
kUCTextBreakIterateMask,
aString, aLength, curOffset, &breakOffset);
kUCTextBreakIterateMask|kUCTextBreakLeadingEdgeMask,
aString, length, breakOffset, &breakOffset);
if (status != noErr) {
UCDisposeTextBreakLocator(&locator);
return;
}
PRUint32 j;
for (j = curOffset + 1; j < breakOffset; ++j) {
aTextRun->SetCharacterGlyph(j, g.SetClusterContinuation());
}
}
NS_ASSERTION(breakOffset == aLength, "Should have found a final break");
++lastBreak;
} while (breakOffset < length);
NS_ASSERTION(breakOffset == length, "Should have found a final break");
UCDisposeTextBreakLocator(&locator);
}
gfxTextRun *
gfxAtsuiFontGroup::MakeTextRunInternal(const PRUnichar *aString, PRUint32 aLength,
Parameters *aParams, PRUint32 aHeaderChars)
PRBool aWrapped, Parameters *aParams)
{
// NS_ASSERTION(!(aParams->mFlags & TEXT_NEED_BOUNDING_BOX),
// "Glyph extents not yet supported");
gfxTextRun *textRun = new gfxTextRun(aParams, aLength);
gfxTextRun *textRun = new gfxTextRun(aParams, aLength - (aWrapped ? 3 : 0));
if (!textRun)
return nsnull;
// There's a one-char header in the string and a one-char trailer
textRun->RecordSurrogates(aString + aHeaderChars);
const PRUnichar *realString = aString + (aWrapped ? 1 : 0);
textRun->RecordSurrogates(realString);
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;
}
@ -422,8 +423,11 @@ gfxAtsuiFontGroup::MakeTextRun(const PRUnichar *aString, PRUint32 aLength,
nsAutoString utf16;
AppendDirectionalIndicator(aParams->mFlags, 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);
return MakeTextRunInternal(utf16.get(), aLength, aParams, 1);
return MakeTextRunInternal(utf16.get(), utf16.Length(), PR_TRUE, aParams);
}
gfxTextRun *
@ -434,16 +438,16 @@ gfxAtsuiFontGroup::MakeTextRun(const PRUint8 *aString, PRUint32 aLength,
nsDependentCSubstring cString(reinterpret_cast<const char*>(aString),
reinterpret_cast<const char*>(aString + aLength));
nsAutoString utf16;
PRUint32 headerChars = 0;
if (aParams->mFlags & TEXT_IS_RTL) {
PRBool wrapBidi = (aParams->mFlags & TEXT_IS_RTL) != 0;
if (wrapBidi) {
AppendDirectionalIndicator(aParams->mFlags, utf16);
headerChars = 1;
}
AppendASCIItoUTF16(cString, utf16);
if (aParams->mFlags & TEXT_IS_RTL) {
if (wrapBidi) {
utf16.Append('.');
utf16.Append(UNICODE_PDF);
}
return MakeTextRunInternal(utf16.get(), aLength, aParams, headerChars);
return MakeTextRunInternal(utf16.get(), utf16.Length(), wrapBidi, aParams);
}
gfxAtsuiFont*
@ -616,7 +620,8 @@ SetGlyphsForCharacterGroup(ATSLayoutRecord *aGlyphs, PRUint32 aGlyphCount,
static void
PostLayoutCallback(ATSULineRef aLine, gfxTextRun *aRun,
const PRUnichar *aString, const PRPackedBool *aUnmatched)
const PRUnichar *aString, PRBool aWrapped,
const PRPackedBool *aUnmatched)
{
// AutoLayoutDataArrayPtr advanceDeltasArray(aLine, kATSUDirectDataAdvanceDeltaFixedArray);
// Fixed *advanceDeltas = NS_STATIC_CAST(Fixed *, advanceDeltasArray.mArray);
@ -640,50 +645,25 @@ PostLayoutCallback(ATSULineRef aLine, gfxTextRun *aRun,
return;
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();
if (isRTL) {
while (numGlyphs > 0 &&
glyphRecords[numGlyphs - 1].originalOffset == stringTailOffset*2 &&
aString[stringTailOffset] == ' ') {
SetGlyphsForCharacterGroup(glyphRecords + numGlyphs - 1, 1,
baselineDeltas ? baselineDeltas + numGlyphs - 1 : nsnull,
appUnitsPerDevUnit, aRun, aUnmatched,
aString);
--stringTailOffset;
--numGlyphs;
}
} else {
while (numGlyphs > 0 &&
glyphRecords[0].originalOffset == stringTailOffset*2 &&
aString[stringTailOffset] == ' ') {
SetGlyphsForCharacterGroup(glyphRecords, 1,
baselineDeltas,
appUnitsPerDevUnit, aRun, aUnmatched,
aString);
--stringTailOffset;
--numGlyphs;
++glyphRecords;
if (aWrapped) {
// 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,
"Couldn't find glyph for trailing marker");
glyphRecords++;
} else {
NS_ASSERTION(glyphRecords[numGlyphs - 1].originalOffset == aRun->GetLength()*2,
"Couldn't find glyph for trailing marker");
}
--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
PRInt32 direction = PRInt32(aRun->GetDirection());
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
// is added to the group containing the vowel.
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
if (glyph->glyphID != ATSUI_SPECIAL_GLYPH_ID) {
// Next character is a normal character, stop the group here
@ -741,6 +727,9 @@ PostLayoutCallback(ATSULineRef aLine, gfxTextRun *aRun,
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;
@ -757,7 +746,8 @@ PostLayoutOperationCallback(ATSULayoutOperationSelector iCurrentOperation,
ATSULayoutOperationCallbackStatus *oCallbackStatus)
{
PostLayoutCallback(iLineRef, gCallbackClosure->mTextRun,
gCallbackClosure->mString, gCallbackClosure->mUnmatchedChars);
gCallbackClosure->mString, gCallbackClosure->mWrapped,
gCallbackClosure->mUnmatchedChars);
*oCallbackStatus = kATSULayoutOperationCallbackStatusContinue;
return noErr;
}
@ -765,30 +755,35 @@ PostLayoutOperationCallback(ATSULayoutOperationSelector iCurrentOperation,
void
gfxAtsuiFontGroup::InitTextRun(gfxTextRun *aRun,
const PRUnichar *aString, PRUint32 aLength,
PRUint32 aHeaderChars)
PRBool aWrapped)
{
OSStatus status;
gfxAtsuiFont *atsuiFont = GetFontAt(0);
ATSUStyle mainStyle = atsuiFont->GetATSUStyle();
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
NS_ConvertUTF16toUTF8 str(aString + 1, aLength);
NS_ConvertUTF16toUTF8 str(realString, realLength);
NS_ConvertUTF16toUTF8 families(mFamilies);
printf("%p(%s) TEXTRUN \"%s\" ENDTEXTRUN\n", this, families.get(), str.get());
#endif
UniCharCount runLengths = aLength;
UniCharCount runLengths = realLength;
ATSUTextLayout layout;
// The string is actually aLength + 2*aHeaderChars chars, with optionally
// a header char to set the direction and a trailer char to pop it. So
// create the text layout giving the whole string as context, although we
// only want glyphs for the inner substring.
// Create the text layout for the whole string, but only produce glyphs
// for the text inside LRO/RLO - PDF, if present. For wrapped strings
// we do need to produce glyphs for the trailing non-whitespace
// character to ensure that ATSUI treats all whitespace as non-trailing.
status = ATSUCreateTextLayoutWithTextPtr
(aString,
aHeaderChars,
headerChars,
realLength + (aWrapped ? 1 : 0),
aLength,
aLength + aHeaderChars*2,
1,
&runLengths,
&mainStyle,
@ -797,8 +792,8 @@ gfxAtsuiFontGroup::InitTextRun(gfxTextRun *aRun,
PostLayoutCallbackClosure closure;
closure.mTextRun = aRun;
// Pass the real string to the closure, ignoring the header
closure.mString = aString + aHeaderChars;
closure.mString = realString;
closure.mWrapped = aWrapped;
NS_ASSERTION(!gCallbackClosure, "Reentering InitTextRun? Expect disaster!");
gCallbackClosure = &closure;
@ -833,9 +828,9 @@ gfxAtsuiFontGroup::InitTextRun(gfxTextRun *aRun,
/* Now go through and update the styles for the text, based on font matching. */
UniCharArrayOffset runStart = aHeaderChars;
UniCharCount totalLength = aLength + aHeaderChars;
UniCharCount runLength = aLength;
UniCharArrayOffset runStart = headerChars;
UniCharCount totalLength = runStart + realLength;
UniCharCount runLength = realLength;
//fprintf (stderr, "==== Starting font maching [string length: %d]\n", totalLength);
while (runStart < totalLength) {
@ -848,7 +843,7 @@ gfxAtsuiFontGroup::InitTextRun(gfxTextRun *aRun,
if (status == noErr) {
//fprintf (stderr, "ATSUMatchFontsToText returned noErr\n");
// everything's good, finish up
aRun->AddGlyphRun(atsuiFont, runStart - aHeaderChars);
aRun->AddGlyphRun(atsuiFont, runStart - headerChars);
break;
} else if (status == kATSUFontsMatched) {
//fprintf (stderr, "ATSUMatchFontsToText returned kATSUFontsMatched: FID %d\n", substituteFontID);
@ -864,14 +859,14 @@ gfxAtsuiFontGroup::InitTextRun(gfxTextRun *aRun,
ATSUSetAttributes (subStyle, 1, fontTags, fontArgSizes, fontArgs);
if (changedOffset > runStart) {
aRun->AddGlyphRun(atsuiFont, runStart - aHeaderChars);
aRun->AddGlyphRun(atsuiFont, runStart - headerChars);
}
ATSUSetRunStyle (layout, subStyle, changedOffset, changedLength);
gfxAtsuiFont *font = FindFontFor(substituteFontID);
if (font) {
aRun->AddGlyphRun(font, changedOffset - aHeaderChars);
aRun->AddGlyphRun(font, changedOffset - headerChars);
}
stylesToDispose.AppendElement(subStyle);
@ -879,7 +874,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 - aHeaderChars);
aRun->AddGlyphRun(atsuiFont, runStart - headerChars);
if (!closure.mUnmatchedChars) {
closure.mUnmatchedChars = new PRPackedBool[aLength];
@ -888,7 +883,7 @@ gfxAtsuiFontGroup::InitTextRun(gfxTextRun *aRun,
}
}
if (closure.mUnmatchedChars) {
memset(closure.mUnmatchedChars.get() + changedOffset - aHeaderChars,
memset(closure.mUnmatchedChars.get() + changedOffset - headerChars,
PR_TRUE, changedLength);
}
}
@ -903,7 +898,7 @@ gfxAtsuiFontGroup::InitTextRun(gfxTextRun *aRun,
// the result of this call.
ATSTrapezoid trap;
ItemCount trapCount;
ATSUGetGlyphBounds(layout, 0, 0, aHeaderChars, aLength,
ATSUGetGlyphBounds(layout, 0, 0, headerChars, realLength,
kATSUseFractionalOrigins, 1, &trap, &trapCount);
ATSUDisposeTextLayout(layout);