diff --git a/gfx/thebes/public/gfxAtsuiFonts.h b/gfx/thebes/public/gfxAtsuiFonts.h index c2e62e8a0190..0fd48f92ed85 100644 --- a/gfx/thebes/public/gfxAtsuiFonts.h +++ b/gfx/thebes/public/gfxAtsuiFonts.h @@ -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; }; diff --git a/gfx/thebes/src/gfxAtsuiFonts.cpp b/gfx/thebes/src/gfxAtsuiFonts.cpp index bd49e2cbdb27..0a9118de7bb8 100644 --- a/gfx/thebes/src/gfxAtsuiFonts.cpp +++ b/gfx/thebes/src/gfxAtsuiFonts.cpp @@ -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(aString), reinterpret_cast(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 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 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);