/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is Novell code. * * The Initial Developer of the Original Code is Novell Corporation. * Portions created by the Initial Developer are Copyright (C) 2006 * the Initial Developer. All Rights Reserved. * * Contributor(s): * robert@ocallahan.org * * Alternatively, the contents of this file may be used under the terms of * either the GNU General Public License Version 2 or later (the "GPL"), or * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsTextRunTransformations.h" #include "nsTextTransformer.h" #include "nsTextFrameUtils.h" #include "gfxSkipChars.h" #include "nsICaseConversion.h" #include "nsStyleConsts.h" #include "nsStyleContext.h" #include "gfxContext.h" #define SZLIG 0x00DF // ============================== // First define some useful textrun wrapper implementations // ============================== class TextProviderWrapper : public gfxTextRun::TextProvider { public: virtual void ForceRememberText() { NS_ERROR("We should never be asked to recover text, we already forced the textrun to remember it"); } }; class ProviderWrapper : public gfxTextRun::PropertyProvider { public: ProviderWrapper(PropertyProvider* aProvider) : mInner(aProvider) {} virtual void ForceRememberText() { NS_ERROR("We should never be asked to recover text, we already forced the textrun to remember it"); } // Simple forwardings virtual gfxFloat GetHyphenWidth() { return mInner->GetHyphenWidth(); } protected: PropertyProvider* mInner; }; class WrapperTextRun : public gfxTextRun { public: WrapperTextRun(gfxTextRun* aInner, gfxTextRunFactory::Parameters* aParams) // WrapperTextRuns always take Unicode : gfxTextRun(aParams, PR_FALSE), mInner(aInner) {} virtual void DrawSpecialString(gfxContext *aContext, gfxPoint aPt, SpecialString aString) { mInner->DrawSpecialString(aContext, aPt, aString); } virtual gfxFloat GetAdvanceWidthSpecialString(SpecialString aString) { return mInner->GetAdvanceWidthSpecialString(aString); } virtual Metrics MeasureTextSpecialString(SpecialString aString, PRBool aTightBoundingBox) { return mInner->MeasureTextSpecialString(aString, aTightBoundingBox); } virtual gfxFont::Metrics GetDecorationMetrics() { return mInner->GetDecorationMetrics(); } protected: nsAutoPtr mInner; }; /** * SZLIG is a problem. When uppercased it turns into "SS", yes, two S's. * * This adds a special wrinkle because normally DOM text only shrinks when * being converted for display. Instead of burdening general DOM<->displaytext * string offset conversion with the possibility of SZLIG-driven growth, we put * all the pain right here in a special text run that knows where extra characters * have been inserted in a string and makes the client believe that they're not * there by adjusting character offsets. * * In general this class could be used to handle text runs that are derived from * some base string by inserting characters. */ class nsGermanTextRun : public WrapperTextRun { public: /** * The gfxSkipCharsBuilder used here represents the characters that should * be removed from the tranformed text in aInnerTextRun to obtain the * original string. */ nsGermanTextRun(gfxTextRun* aInnerTextRun, gfxSkipChars* aSkipChars, gfxTextRunFactory::Parameters* aParams) : WrapperTextRun(aInnerTextRun, aParams) { mTransform.TakeFrom(aSkipChars); } class GermanProviderWrapper : public ProviderWrapper { public: GermanProviderWrapper(PropertyProvider* aProvider, const nsGermanTextRun& aRun) : ProviderWrapper(aProvider), mRun(aRun) {} virtual void GetHyphenationBreaks(PRUint32 aStart, PRUint32 aLength, PRPackedBool* aBreakBefore); virtual void GetSpacing(PRUint32 aStart, PRUint32 aLength, Spacing* aSpacing); protected: const nsGermanTextRun& mRun; }; // Fairly simple forwarding when just offsets or a provider have to be translated virtual PRUint8 GetCharFlags(PRUint32 aOffset) { TranslateOffset(&aOffset); return mInner->GetCharFlags(aOffset); } virtual PRUint32 GetLength() { // mTransform's 'original' characters are actually the *expanded* characters return mTransform.GetOriginalCharCount(); } virtual void Draw(gfxContext* aContext, gfxPoint aPt, PRUint32 aStart, PRUint32 aLength, const gfxRect* aDirtyRect, PropertyProvider* aBreakProvider, gfxFloat* aAdvanceWidth) { GermanProviderWrapper wrapper(aBreakProvider, *this); TranslateSubstring(&aStart, &aLength); mInner->Draw(aContext, aPt, aStart, aLength, aDirtyRect, &wrapper, aAdvanceWidth); } virtual void DrawToPath(gfxContext* aContext, gfxPoint aPt, PRUint32 aStart, PRUint32 aLength, PropertyProvider* aBreakProvider, gfxFloat* aAdvanceWidth) { GermanProviderWrapper wrapper(aBreakProvider, *this); TranslateSubstring(&aStart, &aLength); mInner->DrawToPath(aContext, aPt, aStart, aLength, &wrapper, aAdvanceWidth); } virtual Metrics MeasureText(PRUint32 aStart, PRUint32 aLength, PRBool aTightBoundingBox, PropertyProvider* aBreakProvider) { GermanProviderWrapper wrapper(aBreakProvider, *this); TranslateSubstring(&aStart, &aLength); return mInner->MeasureText(aStart, aLength, aTightBoundingBox, &wrapper); } virtual void SetLineBreaks(PRUint32 aStart, PRUint32 aLength, PRBool aLineBreakBefore, PRBool aLineBreakAfter, TextProvider* aProvider, gfxFloat* aAdvanceWidthDelta) { TextProviderWrapper dummyTextProvider; TranslateSubstring(&aStart, &aLength); return mInner->SetLineBreaks(aStart, aLength, aLineBreakBefore, aLineBreakAfter, &dummyTextProvider, aAdvanceWidthDelta); } virtual gfxFloat GetAdvanceWidth(PRUint32 aStart, PRUint32 aLength, PropertyProvider* aBreakProvider) { GermanProviderWrapper wrapper(aBreakProvider, *this); TranslateSubstring(&aStart, &aLength); return mInner->GetAdvanceWidth(aStart, aLength, &wrapper); } virtual void FlushSpacingCache(PRUint32 aStart) { TranslateOffset(&aStart); mInner->FlushSpacingCache(aStart); } // Tricky forwarding where output conversion is required virtual void GetCharFlags(PRUint32 aStart, PRUint32 aLength, PRUint8* aFlags); virtual PRBool SetPotentialLineBreaks(PRUint32 aStart, PRUint32 aLength, PRPackedBool* aBreakBefore); virtual PRUint32 BreakAndMeasureText(PRUint32 aStart, PRUint32 aMaxLength, PRBool aBreakBefore, gfxFloat aWidth, PropertyProvider* aBreakProvider, PRBool aSuppressInitialBreak, Metrics* aMetrics, PRBool aTightBoundingBox, PRBool* aUsedHyphenation, PRUint32* aLastBreak); friend class GermanProviderWrapper; private: // Translates an original string offset to an offset in the converted string void TranslateOffset(PRUint32* aOffset); // Translates original string substring offsets to substring offsets in the // converted string void TranslateSubstring(PRUint32* aStart, PRUint32* aLength); // Translates converted string substring offsets to substring offsets in the // original string void UntranslateSubstring(PRUint32* aStart, PRUint32* aLength) const; gfxSkipChars mTransform; }; void nsGermanTextRun::TranslateOffset(PRUint32* aOffset) { gfxSkipCharsIterator iter(mTransform); iter.SetSkippedOffset(*aOffset); *aOffset = iter.GetOriginalOffset(); } void nsGermanTextRun::TranslateSubstring(PRUint32* aStart, PRUint32* aLength) { gfxSkipCharsIterator iter(mTransform); PRUint32 end = *aStart + *aLength; iter.SetSkippedOffset(*aStart); *aStart = iter.GetOriginalOffset(); iter.SetSkippedOffset(end); *aLength = iter.GetOriginalOffset() - *aStart; } void nsGermanTextRun::UntranslateSubstring(PRUint32* aStart, PRUint32* aLength) const { gfxSkipCharsIterator iter(mTransform); PRUint32 end = *aStart + *aLength; iter.SetOriginalOffset(*aStart); *aStart = iter.GetSkippedOffset(); iter.SetOriginalOffset(end); *aLength = iter.GetSkippedOffset() - *aStart; } void nsGermanTextRun::GetCharFlags(PRUint32 aStart, PRUint32 aLength, PRUint8* aFlags) { PRUint32 convertedStart = aStart; PRUint32 convertedLength = aLength; TranslateSubstring(&convertedStart, &convertedLength); nsAutoTArray buffer; if (!buffer.AppendElements(convertedLength)) { memset(aFlags, 0, aLength); return; } mInner->GetCharFlags(convertedStart, convertedLength, buffer.Elements()); gfxSkipCharsIterator iter(mTransform); nsSkipCharsRunIterator run(iter, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength); // Iterate over the original-string runs. The client simply never sees // flags for the extra Ss, since they don't exist as far as the client knows. while (run.NextRun()) { memcpy(aFlags + run.GetSkippedOffset() - aStart, buffer.Elements() + run.GetOriginalOffset() - convertedStart, run.GetRunLength()); } } PRBool nsGermanTextRun::SetPotentialLineBreaks(PRUint32 aStart, PRUint32 aLength, PRPackedBool* aBreakBefore) { PRUint32 convertedStart = aStart; PRUint32 convertedLength = aLength; TranslateSubstring(&convertedStart, &convertedLength); nsAutoTArray buffer; if (!buffer.AppendElements(convertedLength)) return PR_TRUE; gfxSkipCharsIterator iter(mTransform); nsSkipCharsRunIterator run(iter, nsSkipCharsRunIterator::LENGTH_UNSKIPPED_ONLY, aLength); while (run.NextRun()) { memcpy(buffer.Elements() + run.GetOriginalOffset() - convertedStart, aBreakBefore + run.GetSkippedOffset() - aStart, run.GetRunLength()); } return mInner->SetPotentialLineBreaks(convertedStart, convertedLength, buffer.Elements()); } PRUint32 nsGermanTextRun::BreakAndMeasureText(PRUint32 aStart, PRUint32 aMaxLength, PRBool aLineBreakBefore, gfxFloat aWidth, PropertyProvider* aBreakProvider, PRBool aSuppressInitialBreak, Metrics* aMetrics, PRBool aTightBoundingBox, PRBool* aUsedHyphenation, PRUint32* aLastBreak) { GermanProviderWrapper wrapper(aBreakProvider, *this); PRUint32 convertedStart = aStart; PRUint32 convertedLength = aMaxLength; TranslateSubstring(&convertedStart, &convertedLength); PRUint32 lastBreak; PRUint32 result = mInner->BreakAndMeasureText(convertedStart, convertedLength, aLineBreakBefore, aWidth, &wrapper, aSuppressInitialBreak, aMetrics, aTightBoundingBox, aUsedHyphenation, &lastBreak); gfxSkipCharsIterator iter(mTransform); iter.SetOriginalOffset(convertedStart + lastBreak); *aLastBreak = iter.GetSkippedOffset() - aStart; iter.SetOriginalOffset(convertedStart + result); return iter.GetSkippedOffset() - aStart; } void nsGermanTextRun::GermanProviderWrapper::GetHyphenationBreaks(PRUint32 aStart, PRUint32 aLength, PRPackedBool* aBreakBefore) { PRUint32 unconvertedStart = aStart; PRUint32 unconvertedLength = aLength; mRun.UntranslateSubstring(&unconvertedStart, &unconvertedLength); // I'm being a bit clever here and reusing aBreakBefore since it's at least // as big as the temporary buffer we need. We put our temporary buffer // at the end of aBreakBefore so that when we start filling aBreakBefore // below, we don't overwrite data we need --- until the very last memmove, // which will be in-place. NS_ASSERTION(aLength >= unconvertedLength, "We only handle *growing* strings here"); PRPackedBool* buffer = aBreakBefore + aLength - unconvertedLength; mInner->GetHyphenationBreaks(unconvertedStart, unconvertedLength, buffer); gfxSkipCharsIterator iter(mRun.mTransform, aStart); nsSkipCharsRunIterator run(iter, nsSkipCharsRunIterator::LENGTH_INCLUDES_SKIPPED, aLength); run.SetVisitSkipped(); while (run.NextRun()) { if (run.IsSkipped()) { // Disallow hyphenation inside the SS memset(aBreakBefore + run.GetOriginalOffset() - aStart, PR_FALSE, run.GetRunLength()); } else { memmove(aBreakBefore + run.GetOriginalOffset() - aStart, buffer + run.GetSkippedOffset() - unconvertedStart, run.GetRunLength()); } } } void nsGermanTextRun::GermanProviderWrapper::GetSpacing(PRUint32 aStart, PRUint32 aLength, Spacing* aSpacing) { PRUint32 unconvertedStart = aStart; PRUint32 unconvertedLength = aLength; mRun.UntranslateSubstring(&unconvertedStart, &unconvertedLength); // I'm being a bit clever here and reusing aSpacing since it's at least // as big as the temporary buffer we need. We put our temporary buffer // at the end of aSpacing so that when we start filling aSpacing // below, we don't overwrite data we need --- until the very last memmove, // which will be in-place. NS_ASSERTION(aLength >= unconvertedLength, "We only handle *growing* strings here"); Spacing* buffer = aSpacing + aLength - unconvertedLength; mInner->GetSpacing(unconvertedStart, unconvertedLength, buffer); gfxSkipCharsIterator iter(mRun.mTransform, aStart); nsSkipCharsRunIterator run(iter, nsSkipCharsRunIterator::LENGTH_INCLUDES_SKIPPED, aLength); run.SetVisitSkipped(); // The following code relies on the fact that the only spacing that can be // added after a SZLIG is letter-spacing, and the same space should be added // after both S's as was addeded after the SZLIG. gfxFloat lastSpaceAfter = 0; while (run.NextRun()) { if (run.IsSkipped()) { PRUint32 offset = run.GetOriginalOffset() - aStart; PRInt32 i; for (i = 0; i < run.GetRunLength(); ++i) { aSpacing[offset + i].mBefore = 0; aSpacing[offset + i].mAfter = lastSpaceAfter; } } else { memcpy(aSpacing + run.GetOriginalOffset() - aStart, buffer + run.GetSkippedOffset() - unconvertedStart, run.GetRunLength()*sizeof(Spacing)); lastSpaceAfter = aSpacing[run.GetOriginalOffset() - aStart + run.GetRunLength() - 1].mAfter; } } } /** * This textrun class encapsulates the concatenation of a list of text runs. This * allows text with runs of different styles (e.g., fonts) to be presented * as a single text run. */ class nsMultiTextRun : public WrapperTextRun { public: nsMultiTextRun(gfxTextRun* aBaseTextRun, gfxTextRunFactory::Parameters* aParams) : WrapperTextRun(aBaseTextRun, aParams) {} struct ChildRun { void Set(gfxTextRun* aChild, PRUint32 aOffset, PRUint32 aLength) { mChild = aChild; mOffset = aOffset; mLength = aLength; } nsAutoPtr mChild; PRUint32 mOffset; PRUint32 mLength; }; // Creates a textrun and adds it to our list. nsresult AddChildRun(gfxTextRunFactory* aFactory, const PRUnichar* aText, PRUint32 aLength, gfxTextRunFactory::Parameters* aParams); // Clears the list of child runs so that it can be regenerated. void ClearChildren() { mRuns.Clear(); } nsresult AddChildRun(gfxTextRun* aChild, PRUint32 aOffset, PRUint32 aLength) { ChildRun* run = mRuns.AppendElement(); if (!run) return NS_ERROR_OUT_OF_MEMORY; run->Set(aChild, aOffset, aLength); return NS_OK; } class MultiProviderWrapper : public ProviderWrapper { public: MultiProviderWrapper(PropertyProvider* aProvider, const nsMultiTextRun& aRun) : ProviderWrapper(aProvider), mRun(aRun) {} void SetChildRun(ChildRun* aRun) { mChild = aRun; } virtual void GetHyphenationBreaks(PRUint32 aStart, PRUint32 aLength, PRPackedBool* aBreakBefore) { mInner->GetHyphenationBreaks(mChild->mOffset + aStart, aLength, aBreakBefore); } virtual void GetSpacing(PRUint32 aStart, PRUint32 aLength, Spacing* aSpacing) { mInner->GetSpacing(mChild->mOffset + aStart, aLength, aSpacing); } protected: const nsMultiTextRun& mRun; ChildRun* mChild; }; virtual PRUint8 GetCharFlags(PRUint32 aOffset); virtual PRUint32 GetLength(); virtual PRBool SetPotentialLineBreaks(PRUint32 aStart, PRUint32 aLength, PRPackedBool* aBreakBefore); virtual void Draw(gfxContext* aContext, gfxPoint aPt, PRUint32 aStart, PRUint32 aLength, const gfxRect* aDirtyRect, PropertyProvider* aBreakProvider, gfxFloat* aAdvanceWidth); virtual void DrawToPath(gfxContext* aContext, gfxPoint aPt, PRUint32 aStart, PRUint32 aLength, PropertyProvider* aBreakProvider, gfxFloat* aAdvanceWidth); virtual Metrics MeasureText(PRUint32 aStart, PRUint32 aLength, PRBool aTightBoundingBox, PropertyProvider* aBreakProvider); virtual void SetLineBreaks(PRUint32 aStart, PRUint32 aLength, PRBool aLineBreakBefore, PRBool aLineBreakAfter, TextProvider* aProvider, gfxFloat* aAdvanceWidthDelta); virtual gfxFloat GetAdvanceWidth(PRUint32 aStart, PRUint32 aLength, PropertyProvider* aBreakProvider); virtual void FlushSpacingCache(PRUint32 aStart); virtual void GetCharFlags(PRUint32 aStart, PRUint32 aLength, PRUint8* aFlags); virtual PRUint32 BreakAndMeasureText(PRUint32 aStart, PRUint32 aMaxLength, PRBool aLineBreakBefore, gfxFloat aWidth, PropertyProvider* aBreakProvider, PRBool aSuppressInitialBreak, Metrics* aMetrics, PRBool aTightBoundingBox, PRBool* aUsedHyphenation, PRUint32* aLastBreak); friend class MultiProviderWrapper; private: PRUint32 FindChildIndexContaining(PRUint32 aOffset); ChildRun* FindChildContaining(PRUint32 aOffset) { return &mRuns[FindChildIndexContaining(aOffset)]; } class ChildIterator { public: ChildIterator(nsMultiTextRun& aMulti, PRUint32 aOffset, PRUint32 aLength, MultiProviderWrapper* aWrapper = nsnull); PRBool NextChild(); ChildRun* GetChild() { return mChild; } gfxTextRun* GetTextRun() { return mChild->mChild; } PRUint32 GetChildOffset() { return mChildOffset; } PRUint32 GetChildLength() { return mChildLength; } PRUint32 GetOffset() { return mOffset; } private: nsMultiTextRun& mMulti; MultiProviderWrapper* mWrapper; ChildRun* mChild; PRUint32 mIndex; PRUint32 mOffset; PRUint32 mRemaining; PRUint32 mChildOffset; PRUint32 mChildLength; }; friend class ChildIterator; void CombineMetrics(gfxTextRun::Metrics* aRunning, const gfxTextRun::Metrics& aMetrics); nsTArray mRuns; }; nsMultiTextRun::ChildIterator::ChildIterator(nsMultiTextRun& aMulti, PRUint32 aOffset, PRUint32 aLength, MultiProviderWrapper* aWrapper) : mMulti(aMulti), mWrapper(aWrapper), mRemaining(aLength) { mIndex = mMulti.FindChildIndexContaining(aOffset); } PRBool nsMultiTextRun::ChildIterator::NextChild() { if (mRemaining <= 0) return PR_FALSE; NS_ASSERTION(mIndex < mMulti.mRuns.Length(), "substring overrun"); mChild = &mMulti.mRuns[mIndex]; ++mIndex; mChildOffset = PR_MAX(mChild->mOffset, mOffset) - mChild->mOffset; mChildLength = PR_MIN(mChild->mLength, mChildOffset + mRemaining); mOffset += mChildLength; mRemaining -= mChildLength; if (mWrapper) { mWrapper->SetChildRun(mChild); } return PR_TRUE; } PRUint32 nsMultiTextRun::GetLength() { if (mRuns.Length() == 0) return 0; ChildRun* last = &mRuns[mRuns.Length() - 1]; return last->mOffset + last->mLength; } nsresult nsMultiTextRun::AddChildRun(gfxTextRunFactory* aFactory, const PRUnichar* aText, PRUint32 aLength, gfxTextRunFactory::Parameters* aParams) { PRUint32 offset = GetLength(); // Update line break list in aParams for the creation of the child. We maintain // the invariant that aParams->mInitialBreaks points to the breaks for the // next child to be added and subsequent children. PRUint32 breakCount = 0; PRUint32 currentBreakCount = aParams->mInitialBreakCount; while (breakCount < currentBreakCount && aParams->mInitialBreaks[breakCount] <= offset + aLength) { // Get the break position into child-textrun coordinates aParams->mInitialBreaks[breakCount] -= offset; ++breakCount; } aParams->mInitialBreakCount = breakCount; gfxTextRun* run = aFactory->MakeTextRun(aText, aLength, aParams); if (!run) return PR_FALSE; // force child to remember its text so we don't have to support recovering it run->RememberText(aText, aLength); // Now update line break list in aParams for the next iteration // If there's a break at the end of this child, leave it in the list so // that the next child will see it at its start. if (breakCount > 0 && aParams->mInitialBreaks[breakCount - 1] == aLength) { // restore to global-textrun coordinates. The rest of the breaks are // skipped, never to be used again, so we don't need to restore them. aParams->mInitialBreaks[breakCount - 1] = offset + aLength; --breakCount; } aParams->mInitialBreaks += breakCount; aParams->mInitialBreakCount -= breakCount; return AddChildRun(run, offset, aLength); } PRUint32 nsMultiTextRun::FindChildIndexContaining(PRUint32 aOffset) { PRUint32 start = 0; PRUint32 end = mRuns.Length(); while (end - start > 1) { PRUint32 mid = (start + end)/2; if (mRuns[mid].mOffset <= aOffset) { start = mid; } else { end = mid; } } NS_ASSERTION(mRuns[start].mOffset + mRuns[start].mLength > aOffset, "Substring offset out of range!"); return start; } void nsMultiTextRun::CombineMetrics(gfxTextRun::Metrics* aRunning, const gfxTextRun::Metrics& aMetrics) { if (IsRightToLeft()) { // 'aRunning' is to the right of 'aMetrics' gfxTextRun::Metrics tmp = aMetrics; tmp.CombineWith(*aRunning); *aRunning = tmp; } else { // 'aRunning' is to the left of 'aMetrics' aRunning->CombineWith(aMetrics); } } PRUint8 nsMultiTextRun::GetCharFlags(PRUint32 aOffset) { ChildRun* child = FindChildContaining(aOffset); return mInner->GetCharFlags(aOffset - child->mOffset); } void nsMultiTextRun::GetCharFlags(PRUint32 aStart, PRUint32 aLength, PRUint8* aFlags) { ChildIterator iter(*this, aStart, aLength); while (iter.NextChild()) { iter.GetTextRun()->GetCharFlags(iter.GetChildOffset(), iter.GetChildLength(), aFlags); aFlags += iter.GetChildLength(); } } PRBool nsMultiTextRun::SetPotentialLineBreaks(PRUint32 aStart, PRUint32 aLength, PRPackedBool* aBreakBefore) { ChildIterator iter(*this, aStart, aLength); PRBool changed = PR_FALSE; while (iter.NextChild()) { changed |= iter.GetTextRun()->SetPotentialLineBreaks(iter.GetChildOffset(), iter.GetChildLength(), aBreakBefore); aBreakBefore += iter.GetChildLength(); } return changed; } void nsMultiTextRun::Draw(gfxContext* aContext, gfxPoint aPt, PRUint32 aStart, PRUint32 aLength, const gfxRect* aDirtyRect, PropertyProvider* aBreakProvider, gfxFloat* aAdvanceWidth) { MultiProviderWrapper wrapper(aBreakProvider, *this); ChildIterator iter(*this, aStart, aLength, &wrapper); gfxFloat totalAdvance = 0; while (iter.NextChild()) { gfxFloat advance; gfxPoint pt = gfxPoint(aPt.x + iter.GetTextRun()->GetDirection()*totalAdvance, aPt.y); iter.GetTextRun()->Draw(aContext, pt, iter.GetChildOffset(), iter.GetChildLength(), aDirtyRect, &wrapper, &advance); totalAdvance += advance; } if (aAdvanceWidth) { *aAdvanceWidth = totalAdvance; } } void nsMultiTextRun::DrawToPath(gfxContext* aContext, gfxPoint aPt, PRUint32 aStart, PRUint32 aLength, PropertyProvider* aBreakProvider, gfxFloat* aAdvanceWidth) { MultiProviderWrapper wrapper(aBreakProvider, *this); ChildIterator iter(*this, aStart, aLength, &wrapper); gfxFloat totalAdvance = 0; while (iter.NextChild()) { gfxFloat advance; gfxPoint pt = gfxPoint(aPt.x + iter.GetTextRun()->GetDirection()*totalAdvance, aPt.y); iter.GetTextRun()->DrawToPath(aContext, pt, iter.GetChildOffset(), iter.GetChildLength(), &wrapper, &advance); totalAdvance += advance; } if (aAdvanceWidth) { *aAdvanceWidth = totalAdvance; } } gfxTextRun::Metrics nsMultiTextRun::MeasureText(PRUint32 aStart, PRUint32 aLength, PRBool aTightBoundingBox, PropertyProvider* aBreakProvider) { MultiProviderWrapper wrapper(aBreakProvider, *this); ChildIterator iter(*this, aStart, aLength, &wrapper); gfxTextRun::Metrics result; while (iter.NextChild()) { gfxTextRun::Metrics metrics = iter.GetTextRun()->MeasureText( iter.GetChildOffset(), iter.GetChildLength(), aTightBoundingBox, &wrapper); CombineMetrics(&result, metrics); } return result; } PRUint32 nsMultiTextRun::BreakAndMeasureText(PRUint32 aStart, PRUint32 aMaxLength, PRBool aBreakBefore, gfxFloat aWidth, PropertyProvider* aBreakProvider, PRBool aSuppressInitialBreak, Metrics* aMetrics, PRBool aTightBoundingBox, PRBool* aUsedHyphenation, PRUint32* aLastBreak) { MultiProviderWrapper wrapper(aBreakProvider, *this); ChildIterator iter(*this, aStart, aMaxLength, &wrapper); PRUint32 fitSoFar = 0; gfxTextRun::Metrics totalMetrics; PRUint32 lastBreak = aMaxLength; while (iter.NextChild()) { gfxTextRun::Metrics metrics; PRUint32 childLastBreak; PRUint32 fits = iter.GetTextRun()->BreakAndMeasureText( iter.GetChildOffset(), iter.GetChildLength(), aBreakBefore, aWidth - aMetrics->mAdvanceWidth, &wrapper, aSuppressInitialBreak, &metrics, aTightBoundingBox, aUsedHyphenation, &childLastBreak); CombineMetrics(&totalMetrics, metrics); if (childLastBreak < iter.GetChildLength()) { lastBreak = fitSoFar + childLastBreak; } fitSoFar += fits; if (fits < iter.GetChildLength()) break; aBreakBefore = PR_FALSE; aSuppressInitialBreak = PR_FALSE; } if (aMetrics) { *aMetrics = totalMetrics; } if (aLastBreak) { *aLastBreak = lastBreak; } return fitSoFar; } void nsMultiTextRun::SetLineBreaks(PRUint32 aStart, PRUint32 aLength, PRBool aLineBreakBefore, PRBool aLineBreakAfter, TextProvider* aProvider, gfxFloat* aAdvanceWidthDelta) { TextProviderWrapper dummyTextProvider; ChildIterator iter(*this, aStart, aLength); gfxFloat delta = 0; while (iter.NextChild()) { gfxFloat thisDelta; iter.GetTextRun()->SetLineBreaks( iter.GetChildOffset(), iter.GetChildLength(), aLineBreakBefore && iter.GetOffset() == aStart, aLineBreakAfter && iter.GetOffset() + iter.GetChildLength() == aStart + aLength, &dummyTextProvider, &thisDelta); delta += thisDelta; } *aAdvanceWidthDelta = delta; } gfxFloat nsMultiTextRun::GetAdvanceWidth(PRUint32 aStart, PRUint32 aLength, PropertyProvider* aBreakProvider) { MultiProviderWrapper wrapper(aBreakProvider, *this); ChildIterator iter(*this, aStart, aLength, &wrapper); gfxFloat result; while (iter.NextChild()) { result += iter.GetTextRun()->GetAdvanceWidth( iter.GetChildOffset(), iter.GetChildLength(), &wrapper); } return result; } void nsMultiTextRun::FlushSpacingCache(PRUint32 aStart) { PRUint32 index = FindChildIndexContaining(aStart); ChildRun* child = &mRuns[index]; child->mChild->FlushSpacingCache(aStart - child->mOffset); while (index + 1 < mRuns.Length()) { ++index; child = &mRuns[index]; child->mChild->FlushSpacingCache(0); } } // ============================== // Now define factories that use the above textruns // ============================== static gfxTextRunFactory::Parameters GetParametersForInner(const gfxTextRunFactory::Parameters& aParams, gfxSkipChars* aDummy) { gfxTextRunFactory::Parameters params = { aParams.mContext, nsnull, aParams.mLangGroup, aDummy, aParams.mInitialBreaks, aParams.mInitialBreakCount, aParams.mPixelsToUnits, aParams.mFlags }; return params; } gfxTextRun* nsTransformingTextRunFactory::MakeTextRun(const PRUint8* aString, PRUint32 aLength, gfxTextRunFactory::Parameters* aParams) { // We'll only have a Unicode code path to minimize the amount of code needed // for this rarely used feature NS_ConvertASCIItoUTF16 unicodeString(NS_REINTERPRET_CAST(const char*, aString), aLength); Parameters params = *aParams; params.mFlags &= ~gfxTextRunFactory::TEXT_IS_PERSISTENT; return MakeTextRun(unicodeString.get(), aLength, ¶ms); } /** * For smallcaps we divide the text into runs of lowercase and non-lowercase * clusters and build a nsMultiTextRun for the separate runs. For the lowercase * runs we use nsCaseTransformTextRunFactory to convert to uppercase. */ gfxTextRun* nsFontVariantTextRunFactory::MakeTextRun(const PRUnichar* aString, PRUint32 aLength, gfxTextRunFactory::Parameters* aParams) { nsICaseConversion* converter = nsTextTransformer::GetCaseConv(); if (!converter || !mStyles) return nsnull; gfxFontStyle fontStyle = *mFontGroup->GetStyle(); fontStyle.size *= 0.8; nsRefPtr smallFont = mFontGroup->Copy(&fontStyle); if (!smallFont) return nsnull; gfxSkipChars dummy; gfxTextRunFactory::Parameters innerParams = GetParametersForInner(*aParams, &dummy); gfxTextRun* inner = mFontGroup->MakeTextRun(aString, aLength, &innerParams); if (!inner) return nsnull; // force the inner to remember the text, so we don't have to worry about // reconstructing it later. inner->RememberText(aString, aLength); nsAutoPtr textRun; textRun = new nsMultiTextRun(inner, aParams); if (!textRun) return nsnull; nsRefPtr uppercaseFactory = new nsCaseTransformTextRunFactory(smallFont, nsnull, PR_TRUE); if (!uppercaseFactory) return nsnull; PRUint32 i; PRUint32 runStart = 0; PRPackedBool runIsLowercase = PR_FALSE; for (i = 0; i <= aLength; ++i) { PRBool isLowercase = PR_FALSE; if (i < aLength) { // Characters that aren't the start of a cluster are ignored here. They // get added to whatever lowercase/non-lowercase run we're in. if (!(inner->GetCharFlags(i) & gfxTextRun::CLUSTER_START)) continue; if (mStyles[i]->GetStyleFont()->mFont.variant == NS_STYLE_FONT_VARIANT_SMALL_CAPS) { PRUnichar ch = aString[i]; PRUnichar ch2; converter->ToUpper(ch, &ch2); isLowercase = ch != ch2; } else { // Don't transform the character! I.e., pretend that it's not lowercase } } if ((i == aLength || runIsLowercase != isLowercase) && runStart < i) { gfxTextRunFactory* factory; if (runIsLowercase) { factory = uppercaseFactory; if (mInnerTransformingTextRunFactory) { mInnerTransformingTextRunFactory->SetStyles(mStyles + runStart); } } else { factory = mFontGroup; } nsresult rv = textRun->AddChildRun(factory, aString + runStart, i - runStart, &innerParams); if (NS_FAILED(rv)) return nsnull; runStart = i; runIsLowercase = isLowercase; } } return textRun.forget(); } class nsCaseTransformingTextRun : public nsMultiTextRun { public: nsCaseTransformingTextRun(gfxTextRun* aBaseTextRun, gfxTextRunFactory::Parameters* aParams, nsCaseTransformTextRunFactory* aFactory, const PRUnichar* aString, PRUint32 aLength) : nsMultiTextRun(aBaseTextRun, aParams), mString(aString, aLength), mFactory(aFactory), mRefContext(aParams->mContext), mLangGroup(aParams->mLangGroup), mPixelsToUnits(aParams->mPixelsToUnits), mFlags(aParams->mFlags) { PRUint32 i; for (i = 0; i < aLength; ++i) { mLineBreakOpportunities.AppendElement(PR_FALSE); if (!aFactory->IsAllUppercase()) { mStyles.AppendElement(aFactory->GetStyles()[i]); } } for (i = 0; i < aParams->mInitialBreakCount; ++i) { mLineBreaks.AppendElement(aParams->mInitialBreaks[i]); } } virtual PRBool SetPotentialLineBreaks(PRUint32 aStart, PRUint32 aLength, PRPackedBool* aBreakBefore); virtual void SetLineBreaks(PRUint32 aStart, PRUint32 aLength, PRBool aLineBreakBefore, PRBool aLineBreakAfter, TextProvider* aProvider, gfxFloat* aAdvanceWidthDelta); nsresult Build(); private: nsString mString; nsRefPtr mFactory; nsRefPtr mRefContext; nsCOMPtr mLangGroup; nsTArray mLineBreaks; nsTArray mLineBreakOpportunities; nsTArray > mStyles; gfxFloat mPixelsToUnits; PRUint32 mFlags; }; nsresult nsCaseTransformingTextRun::Build() { nsICaseConversion* converter = nsTextTransformer::GetCaseConv(); if (!converter) return NS_ERROR_FAILURE; nsAutoString convertedString; PRUint32 i; // skipBuilder represents a translation from the *transformed* string to the // *original* string, because in this case, that's the way deletions go gfxSkipCharsBuilder skipBuilder; nsAutoTArray styles; for (i = 0; i < mString.Length(); ++i) { PRUnichar ch = mString.CharAt(i); // We're definitely going to keep at least one char in the hypothetical // "transformation" from original to transformed string skipBuilder.KeepChar(); PRUint8 style = mFactory->IsAllUppercase() ? NS_STYLE_TEXT_TRANSFORM_UPPERCASE : mStyles[i]->GetStyleText()->mTextTransform; switch (style) { case NS_STYLE_TEXT_TRANSFORM_LOWERCASE: converter->ToLower(ch, &ch); break; case NS_STYLE_TEXT_TRANSFORM_UPPERCASE: if (ch == SZLIG) { convertedString.Append('S'); skipBuilder.SkipChar(); ch = 'S'; } else { converter->ToUpper(ch, &ch); } break; case NS_STYLE_TEXT_TRANSFORM_CAPITALIZE: if (mLineBreakOpportunities[i]) { if (ch == SZLIG) { styles.AppendElement(mStyles[i]); convertedString.Append('S'); skipBuilder.SkipChar(); ch = 'S'; } else { converter->ToTitle(ch, &ch); } } break; default: break; } styles.AppendElement(mStyles[i]); convertedString.Append(ch); } PRBool allCharsKept = skipBuilder.GetAllCharsKept(); gfxSkipChars skipChars; skipChars.TakeFrom(&skipBuilder); // Convert line break offsets nsAutoTArray lineBreaks; if (!lineBreaks.AppendElements(mLineBreaks.Length())) return NS_ERROR_OUT_OF_MEMORY; gfxSkipCharsIterator iter(skipChars); for (i = 0; i < mLineBreaks.Length(); ++i) { lineBreaks[i] = iter.ConvertSkippedToOriginal(mLineBreaks[i]); } // Create a textrun for the transformed string gfxSkipChars dummy; gfxTextRunFactory::Parameters innerParams = { mRefContext, nsnull, mLangGroup, &dummy, mLineBreaks.Elements(), mLineBreaks.Length(), mPixelsToUnits, mFlags | gfxTextRunFactory::TEXT_IS_PERSISTENT }; if (mFactory->GetInnerTransformingTextRunFactory()) { mFactory->GetInnerTransformingTextRunFactory()->SetStyles(styles.Elements()); } nsAutoPtr innerTextRun; innerTextRun = mFactory->GetInnerTextRunFactory()->MakeTextRun(convertedString.get(), convertedString.Length(), &innerParams); if (!innerTextRun) return NS_ERROR_FAILURE; // Make sure the inner textrun never calls ForceRememberText, because we don't // implement it innerTextRun->RememberText(convertedString.get(), convertedString.Length()); if (!allCharsKept) { // We need to adjust offsets because characters were inserted. gfxTextRun* wrapper = new nsGermanTextRun(innerTextRun, &skipChars, &innerParams); if (!wrapper) return NS_ERROR_FAILURE; innerTextRun.forget(); innerTextRun = wrapper; } ClearChildren(); nsresult rv = AddChildRun(innerTextRun, 0, convertedString.Length()); if (NS_SUCCEEDED(rv)) { innerTextRun.forget(); } return rv; } PRBool nsCaseTransformingTextRun::SetPotentialLineBreaks(PRUint32 aStart, PRUint32 aLength, PRPackedBool* aBreakBefore) { PRUint32 i; PRBool changed = PR_FALSE; PRBool changedStyle = PR_FALSE; for (i = 0; i < aLength; ++i) { if (mLineBreakOpportunities[aStart + i] != aBreakBefore[i]) { changed = PR_TRUE; if (mStyles[i]->GetStyleText()->mTextTransform == NS_STYLE_TEXT_TRANSFORM_CAPITALIZE) { changedStyle = PR_TRUE; } mLineBreakOpportunities[aStart + i] = aBreakBefore[i]; } } if (!changedStyle) return nsMultiTextRun::SetPotentialLineBreaks(aStart, aLength, aBreakBefore); Build(); return changed; } void nsCaseTransformingTextRun::SetLineBreaks(PRUint32 aStart, PRUint32 aLength, PRBool aLineBreakBefore, PRBool aLineBreakAfter, TextProvider* aProvider, gfxFloat* aAdvanceWidthDelta) { nsTArray newBreaks; for (PRUint32 i = 0; i < mLineBreaks.Length(); ++i) { PRUint32 b = mLineBreaks[i]; if (b == aStart && !aLineBreakBefore) continue; if (b > aStart && aLineBreakBefore) { newBreaks.AppendElement(aStart); aLineBreakBefore = PR_FALSE; } if (b == aStart + aLength && !aLineBreakAfter) continue; if (b > aStart + aLength && aLineBreakAfter) { newBreaks.AppendElement(aStart + aLength); aLineBreakAfter = PR_FALSE; } newBreaks.AppendElement(b); } mLineBreaks.SwapElements(newBreaks); nsMultiTextRun::SetLineBreaks(aStart, aLength, aLineBreakBefore, aLineBreakAfter, aProvider, aAdvanceWidthDelta); } /** * When doing case conversion we first transform the string. If the string * ends up with no extra characters inserted, we can just create a textrun * from the transformed string and hand it back to the caller, because string * offsets are the same. If extra characters are inserted due to * uppercased SZLIG, we have to create a special text run, nsGermanTextRun, * which adjusts character offsets for us. */ gfxTextRun* nsCaseTransformTextRunFactory::MakeTextRun(const PRUnichar* aString, PRUint32 aLength, gfxTextRunFactory::Parameters* aParams) { // Create a textrun for the transformed string gfxSkipChars dummy; gfxTextRunFactory::Parameters innerParams = GetParametersForInner(*aParams, &dummy); innerParams.mFlags &= ~gfxTextRunFactory::TEXT_IS_PERSISTENT; if (mInnerTransformingTextRunFactory) { mInnerTransformingTextRunFactory->SetStyles(mStyles); } // This is the dummy that nsMultiTextRun needs nsAutoPtr innerTextRun; innerTextRun = mInnerTextRunFactory->MakeTextRun(aString, aLength, &innerParams); if (!innerTextRun) return nsnull; // Make sure the inner textrun never calls ForceRememberText, because we don't // implement it innerTextRun->RememberText(aString, aLength); nsAutoPtr textRun; textRun = new nsCaseTransformingTextRun(innerTextRun, aParams, this, aString, aLength); if (!textRun) return nsnull; innerTextRun.forget(); nsresult rv = textRun->Build(); if (NS_FAILED(rv)) return nsnull; return textRun.forget(); }