forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			1185 lines
		
	
	
	
		
			42 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			1185 lines
		
	
	
	
		
			42 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- 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<gfxTextRun> 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<PRUint8,BIG_TEXT_NODE_SIZE> 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<PRPackedBool,BIG_TEXT_NODE_SIZE> 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<gfxTextRun> 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<ChildRun> 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<gfxFontGroup> 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<nsMultiTextRun> textRun;
 | |
|   textRun = new nsMultiTextRun(inner, aParams);
 | |
|   if (!textRun)
 | |
|     return nsnull;
 | |
| 
 | |
|   nsRefPtr<nsCaseTransformTextRunFactory> 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<nsCaseTransformTextRunFactory> mFactory;
 | |
|   nsRefPtr<gfxContext>                    mRefContext;
 | |
|   nsCOMPtr<nsIAtom>                       mLangGroup;
 | |
|   nsTArray<PRUint32>                      mLineBreaks;
 | |
|   nsTArray<PRPackedBool>                  mLineBreakOpportunities;
 | |
|   nsTArray<nsRefPtr<nsStyleContext> >     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<nsStyleContext*,1000> 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<PRUint32,50> 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<gfxTextRun> 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<PRUint32> 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<gfxTextRun> 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<nsCaseTransformingTextRun> 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();
 | |
| }
 | 
