From fa82daaaaf0b4f730bba8fc6907080fc08ce266d Mon Sep 17 00:00:00 2001 From: "roc+%cs.cmu.edu" Date: Tue, 23 Jan 2007 08:45:52 +0000 Subject: [PATCH] Bug 333659. (Re)landing gfx changes: new gfxTextRun interfaces, implementation of gfxPangoTextRun, stub implementations for Mac and Windows, nsThebesRenderingContext reimplemented on top of the new interfaces. r=pavlov --- gfx/src/thebes/Makefile.in | 2 +- gfx/src/thebes/nsIThebesFontMetrics.h | 5 + gfx/src/thebes/nsThebesFontMetrics.cpp | 145 +- gfx/src/thebes/nsThebesFontMetrics.h | 44 +- gfx/src/thebes/nsThebesRenderingContext.cpp | 6 + gfx/src/thebes/nsThebesRenderingContext.h | 1 + gfx/thebes/Makefile.in | 2 +- gfx/thebes/public/gfxAtsuiFonts.h | 17 +- gfx/thebes/public/gfxContext.h | 17 - gfx/thebes/public/gfxFont.h | 508 ++++- gfx/thebes/public/gfxPangoFonts.h | 363 ++- gfx/thebes/public/gfxTextRunCache.h | 27 +- gfx/thebes/public/gfxWindowsFonts.h | 20 +- gfx/thebes/src/gfxAtsuiFonts.cpp | 152 +- gfx/thebes/src/gfxContext.cpp | 8 - gfx/thebes/src/gfxPangoFonts.cpp | 2228 +++++++++++++++---- gfx/thebes/src/gfxTextRunCache.cpp | 135 +- gfx/thebes/src/gfxWindowsFonts.cpp | 155 +- 18 files changed, 3165 insertions(+), 670 deletions(-) diff --git a/gfx/src/thebes/Makefile.in b/gfx/src/thebes/Makefile.in index 463445ad9b7..5058461a383 100644 --- a/gfx/src/thebes/Makefile.in +++ b/gfx/src/thebes/Makefile.in @@ -124,7 +124,7 @@ CPPSRCS += nsSystemFontsMac.cpp endif endif -EXPORTS += nsIThebesRenderingContext.h +EXPORTS += nsIThebesRenderingContext.h nsIThebesFontMetrics.h LOCAL_INCLUDES = \ -I$(srcdir)/. \ diff --git a/gfx/src/thebes/nsIThebesFontMetrics.h b/gfx/src/thebes/nsIThebesFontMetrics.h index 92254bceb6c..3e2d78830bc 100644 --- a/gfx/src/thebes/nsIThebesFontMetrics.h +++ b/gfx/src/thebes/nsIThebesFontMetrics.h @@ -43,6 +43,8 @@ class nsThebesRenderingContext; +class gfxFontGroup; + class nsIThebesFontMetrics : public nsIFontMetrics { public: // Get the width for this string. aWidth will be updated with the @@ -108,8 +110,11 @@ public: // Set the direction of the text rendering virtual nsresult SetRightToLeftText(PRBool aIsRTL) = 0; virtual PRBool GetRightToLeftText() = 0; + virtual void SetTextRunRTL(PRBool aIsRTL) = 0; virtual PRInt32 GetMaxStringLength() = 0; + + virtual gfxFontGroup* GetThebesFontGroup() = 0; }; #endif /* __nsIThebesFontMetrics_h */ diff --git a/gfx/src/thebes/nsThebesFontMetrics.cpp b/gfx/src/thebes/nsThebesFontMetrics.cpp index 36690c1d6f0..3727b9405b6 100644 --- a/gfx/src/thebes/nsThebesFontMetrics.cpp +++ b/gfx/src/thebes/nsThebesFontMetrics.cpp @@ -40,9 +40,10 @@ #include "nsFont.h" #include "nsString.h" -#include "nsAutoBuffer.h" #include +#include "gfxTextRunCache.h" + NS_IMPL_ISUPPORTS1(nsThebesFontMetrics, nsIFontMetrics) #include @@ -55,8 +56,6 @@ NS_IMPL_ISUPPORTS1(nsThebesFontMetrics, nsIFontMetrics) #include "gfxAtsuiFonts.h" #endif -#include "gfxTextRunCache.h" - nsThebesFontMetrics::nsThebesFontMetrics() { mFontStyle = nsnull; @@ -77,7 +76,8 @@ nsThebesFontMetrics::Init(const nsFont& aFont, nsIAtom* aLangGroup, mLangGroup = aLangGroup; mDeviceContext = (nsThebesDeviceContext*)aContext; mDev2App = aContext->DevUnitsToAppUnits(); - mIsRTL = PR_FALSE; + mIsRightToLeft = PR_FALSE; + mTextRunRTL = PR_FALSE; // work around layout giving us 0 sized fonts... double size = aFont.size * mDeviceContext->AppUnitsToDevUnits(); @@ -283,6 +283,43 @@ nsThebesFontMetrics::GetMaxStringLength() return PR_MAX(1, len); } +class StubPropertyProvider : public gfxTextRun::PropertyProvider { +public: + StubPropertyProvider(const nscoord* aSpacing = nsnull) + : mSpacing(aSpacing) {} + + virtual void ForceRememberText() { + NS_ERROR("This shouldn't be called because we already asked the textrun to remember"); + } + virtual void GetHyphenationBreaks(PRUint32 aStart, PRUint32 aLength, + PRPackedBool* aBreakBefore) { + NS_ERROR("This shouldn't be called because we never call BreakAndMeasureText"); + } + virtual gfxFloat GetHyphenWidth() { + NS_ERROR("This shouldn't be called because we never specify hyphen breaks"); + return 0; + } + virtual void GetSpacing(PRUint32 aStart, PRUint32 aLength, + Spacing* aSpacing); + +private: + const nscoord* mSpacing; +}; + +void +StubPropertyProvider::GetSpacing(PRUint32 aStart, PRUint32 aLength, + Spacing* aSpacing) +{ + PRUint32 i; + for (i = 0; i < aLength; ++i) { + aSpacing[i].mBefore = 0; + // mSpacing is absolute (already includes the character width). This is OK + // because gfxTextRunCache sets TEXT_ABSOLUTE_SPACING when it creates + // the textrun. + aSpacing[i].mAfter = mSpacing ? mSpacing[aStart + i] : 0; + } +} + nsresult nsThebesFontMetrics::GetWidth(const char* aString, PRUint32 aLength, nscoord& aWidth, nsThebesRenderingContext *aContext) @@ -296,13 +333,12 @@ nsThebesFontMetrics::GetWidth(const char* aString, PRUint32 aLength, nscoord& aW if ((aLength == 1) && (aString[0] == ' ')) return GetSpaceWidth(aWidth); - const nsDependentCSubstring& theString = nsDependentCSubstring(aString, aString+aLength); - //nsRefPtr textrun = mFontGroup->MakeTextRun(theString); - nsRefPtr textrun = gfxTextRunCache::GetCache()->GetOrMakeTextRun(mFontGroup, theString); + StubPropertyProvider provider; + AutoTextRun textRun(this, aContext, aString, aLength, PR_FALSE); + if (!textRun.get()) + return NS_ERROR_FAILURE; - textrun->SetRightToLeft(mIsRTL); - - aWidth = ROUND_TO_TWIPS(textrun->Measure(aContext->Thebes())); + aWidth = NSToCoordRound(textRun->GetAdvanceWidth(0, aLength, &provider)); return NS_OK; } @@ -321,16 +357,14 @@ nsThebesFontMetrics::GetWidth(const PRUnichar* aString, PRUint32 aLength, if ((aLength == 1) && (aString[0] == ' ')) return GetSpaceWidth(aWidth); - const nsDependentSubstring& theString = nsDependentSubstring(aString, aString+aLength); - //nsRefPtr textrun = mFontGroup->MakeTextRun(theString); - nsRefPtr textrun = gfxTextRunCache::GetCache()->GetOrMakeTextRun(mFontGroup, theString); + StubPropertyProvider provider; + AutoTextRun textRun(this, aContext, aString, aLength, PR_FALSE); + if (!textRun.get()) + return NS_ERROR_FAILURE; - textrun->SetRightToLeft(mIsRTL); - - aWidth = ROUND_TO_TWIPS(textrun->Measure(aContext->Thebes())); + aWidth = NSToCoordRound(textRun->GetAdvanceWidth(0, aLength, &provider)); return NS_OK; - } // Get the text dimensions for this string @@ -380,68 +414,47 @@ nsThebesFontMetrics::DrawString(const char *aString, PRUint32 aLength, if (aLength == 0) return NS_OK; - float app2dev = mDeviceContext->AppUnitsToDevUnits(); - - const nsDependentCSubstring& theString = nsDependentCSubstring(aString, aString+aLength); - //nsRefPtr textrun = mFontGroup->MakeTextRun(theString); - nsRefPtr textrun = gfxTextRunCache::GetCache()->GetOrMakeTextRun(mFontGroup, theString); - - textrun->SetRightToLeft(mIsRTL); - - if (aSpacing) { - gfxFloat offset = aX * app2dev; - nsTArray spacing(aLength); - for (PRUint32 i = 0; i < aLength; ++i) { - gfxFloat nextOffset = offset + aSpacing[i] * app2dev; - spacing.AppendElement(NSToIntRound(nextOffset) - - NSToIntRound(offset)); - offset = nextOffset; - } - textrun->SetSpacing(spacing); + StubPropertyProvider provider(aSpacing); + AutoTextRun textRun(this, aContext, aString, aLength, aSpacing != nsnull); + if (!textRun.get()) + return NS_ERROR_FAILURE; + gfxPoint pt(aX, aY); +#ifdef MOZ_X11 + if (mTextRunRTL) { + pt.x += textRun->GetAdvanceWidth(0, aLength, &provider); } - - aContext->Thebes()->DrawTextRun(textrun, gfxPoint(NSToIntRound(aX * app2dev), NSToIntRound(aY * app2dev))); - +#endif + textRun->Draw(aContext->Thebes(), pt, 0, aLength, + nsnull, &provider, nsnull); return NS_OK; } // aCachedOffset will be updated with a new offset. nsresult nsThebesFontMetrics::DrawString(const PRUnichar* aString, PRUint32 aLength, - nscoord aX, nscoord aY, - PRInt32 aFontID, - const nscoord* aSpacing, - nsThebesRenderingContext *aContext) + nscoord aX, nscoord aY, + PRInt32 aFontID, + const nscoord* aSpacing, + nsThebesRenderingContext *aContext) { if (aLength == 0) return NS_OK; - float app2dev = mDeviceContext->AppUnitsToDevUnits(); - - const nsDependentSubstring& theString = nsDependentSubstring(aString, aString+aLength); - //nsRefPtr textrun = mFontGroup->MakeTextRun(theString); - nsRefPtr textrun = gfxTextRunCache::GetCache()->GetOrMakeTextRun(mFontGroup, theString); - - textrun->SetRightToLeft(mIsRTL); - - if (aSpacing) { - gfxFloat offset = aX * app2dev; - nsTArray spacing(aLength); - for (PRUint32 i = 0; i < aLength; ++i) { - gfxFloat nextOffset = offset + aSpacing[i] * app2dev; - spacing.AppendElement(NSToIntRound(nextOffset) - - NSToIntRound(offset)); - offset = nextOffset; - } - textrun->SetSpacing(spacing); + StubPropertyProvider provider(aSpacing); + AutoTextRun textRun(this, aContext, aString, aLength, aSpacing != nsnull); + if (!textRun.get()) + return NS_ERROR_FAILURE; + gfxPoint pt(aX, aY); +#ifdef MOZ_X11 + if (mTextRunRTL) { + pt.x += textRun->GetAdvanceWidth(0, aLength, &provider); } - - aContext->Thebes()->DrawTextRun(textrun, gfxPoint(NSToIntRound(aX * app2dev), NSToIntRound(aY * app2dev))); - +#endif + textRun->Draw(aContext->Thebes(), pt, 0, aLength, + nsnull, &provider, nsnull); return NS_OK; } - #ifdef MOZ_MATHML // These two functions get the bounding metrics for this handle, // updating the aBoundingMetrics in Points. This means that the @@ -470,7 +483,7 @@ nsThebesFontMetrics::GetBoundingMetrics(const PRUnichar *aString, nsresult nsThebesFontMetrics::SetRightToLeftText(PRBool aIsRTL) { - mIsRTL = aIsRTL; + mIsRightToLeft = aIsRTL; return NS_OK; } @@ -478,5 +491,5 @@ nsThebesFontMetrics::SetRightToLeftText(PRBool aIsRTL) PRBool nsThebesFontMetrics::GetRightToLeftText() { - return mIsRTL; + return mIsRightToLeft; } diff --git a/gfx/src/thebes/nsThebesFontMetrics.h b/gfx/src/thebes/nsThebesFontMetrics.h index ebbbd919ac4..bb2ef39068f 100644 --- a/gfx/src/thebes/nsThebesFontMetrics.h +++ b/gfx/src/thebes/nsThebesFontMetrics.h @@ -46,6 +46,7 @@ #include "nsIAtom.h" #include "gfxFont.h" +#include "gfxTextRunCache.h" class nsThebesFontMetrics : public nsIThebesFontMetrics { @@ -142,11 +143,51 @@ public: // Set the direction of the text rendering virtual nsresult SetRightToLeftText(PRBool aIsRTL); virtual PRBool GetRightToLeftText(); + virtual void SetTextRunRTL(PRBool aIsRTL) { mTextRunRTL = aIsRTL; } + + virtual gfxFontGroup* GetThebesFontGroup() { return mFontGroup; } + + PRBool GetRightToLeftTextRunMode() { +#ifdef MOZ_X11 + return mTextRunRTL; +#else + return mIsRightToLeft; +#endif + } protected: const gfxFont::Metrics& GetMetrics() const; + class AutoTextRun { + public: + AutoTextRun(nsThebesFontMetrics* aMetrics, nsIRenderingContext* aRC, + const char* aString, PRInt32 aLength, PRBool aEnableSpacing) { + mTextRun = gfxTextRunCache::GetCache()->GetOrMakeTextRun( + NS_STATIC_CAST(gfxContext*, aRC->GetNativeGraphicData(nsIRenderingContext::NATIVE_THEBES_CONTEXT)), + aMetrics->mFontGroup, aString, aLength, aMetrics->mDev2App, + aMetrics->GetRightToLeftTextRunMode(), aEnableSpacing, &mOwning); + } + AutoTextRun(nsThebesFontMetrics* aMetrics, nsIRenderingContext* aRC, + const PRUnichar* aString, PRInt32 aLength, PRBool aEnableSpacing) { + mTextRun = gfxTextRunCache::GetCache()->GetOrMakeTextRun( + NS_STATIC_CAST(gfxContext*, aRC->GetNativeGraphicData(nsIRenderingContext::NATIVE_THEBES_CONTEXT)), + aMetrics->mFontGroup, aString, aLength, aMetrics->mDev2App, + aMetrics->GetRightToLeftTextRunMode(), aEnableSpacing, &mOwning); + } + ~AutoTextRun() { + if (mOwning) { + delete mTextRun; + } + } + gfxTextRun* operator->() { return mTextRun; } + gfxTextRun* get() { return mTextRun; } + private: + gfxTextRun* mTextRun; + PRBool mOwning; + }; + friend class AutoTextRun; + nsRefPtr mFontGroup; gfxFontStyle *mFontStyle; @@ -154,7 +195,8 @@ private: nsThebesDeviceContext *mDeviceContext; nsCOMPtr mLangGroup; float mDev2App; - PRBool mIsRTL; + PRPackedBool mIsRightToLeft; + PRPackedBool mTextRunRTL; }; #endif /* NSTHEBESFONTMETRICS__H__ */ diff --git a/gfx/src/thebes/nsThebesRenderingContext.cpp b/gfx/src/thebes/nsThebesRenderingContext.cpp index 2d47b6d32d9..8dabd281f95 100644 --- a/gfx/src/thebes/nsThebesRenderingContext.cpp +++ b/gfx/src/thebes/nsThebesRenderingContext.cpp @@ -1104,6 +1104,12 @@ nsThebesRenderingContext::GetRightToLeftText(PRBool* aIsRTL) return NS_OK; } +void +nsThebesRenderingContext::SetTextRunRTL(PRBool aIsRTL) +{ + mFontMetrics->SetTextRunRTL(aIsRTL); +} + NS_IMETHODIMP nsThebesRenderingContext::SetFont(const nsFont& aFont, nsIAtom* aLangGroup) { diff --git a/gfx/src/thebes/nsThebesRenderingContext.h b/gfx/src/thebes/nsThebesRenderingContext.h index 131ab3ad7c0..c249fd352ed 100644 --- a/gfx/src/thebes/nsThebesRenderingContext.h +++ b/gfx/src/thebes/nsThebesRenderingContext.h @@ -242,6 +242,7 @@ public: const nsRect * aTargetRect); NS_IMETHOD SetRightToLeftText(PRBool aIsRTL); NS_IMETHOD GetRightToLeftText(PRBool* aIsRTL); + virtual void SetTextRunRTL(PRBool aIsRTL); NS_IMETHOD GetClusterInfo(const PRUnichar *aText, PRUint32 aLength, diff --git a/gfx/thebes/Makefile.in b/gfx/thebes/Makefile.in index 0a57d8dc362..fd77f371ea6 100644 --- a/gfx/thebes/Makefile.in +++ b/gfx/thebes/Makefile.in @@ -11,7 +11,7 @@ MODULE = thebes DIRS = public src ifdef ENABLE_TESTS -TOOL_DIRS += test +# TOOL_DIRS += test endif include $(topsrcdir)/config/rules.mk diff --git a/gfx/thebes/public/gfxAtsuiFonts.h b/gfx/thebes/public/gfxAtsuiFonts.h index 23a4a36e0d7..4cc5c47da6f 100644 --- a/gfx/thebes/public/gfxAtsuiFonts.h +++ b/gfx/thebes/public/gfxAtsuiFonts.h @@ -86,10 +86,14 @@ public: const gfxFontStyle *aStyle); virtual ~gfxAtsuiFontGroup(); - virtual gfxTextRun *MakeTextRun(const nsAString& aString); - virtual gfxTextRun *MakeTextRun(const nsACString& aCString) { - return MakeTextRun(NS_ConvertASCIItoUTF16(aCString)); + virtual gfxFontGroup *Copy(const gfxFontStyle *aStyle) { + NS_ERROR("NOT IMPLEMENTED"); + return nsnull; } + virtual gfxTextRun *MakeTextRun(const PRUnichar* aString, PRUint32 aLength, + Parameters* aParams); + virtual gfxTextRun *MakeTextRun(const PRUint8* aString, PRUint32 aLength, + Parameters* aParams); ATSUFontFallbacks *GetATSUFontFallbacksPtr() { return &mFallbacks; } @@ -107,7 +111,7 @@ protected: ATSUFontFallbacks mFallbacks; }; -class THEBES_API gfxAtsuiTextRun : public gfxTextRun { +class THEBES_API gfxAtsuiTextRun { public: gfxAtsuiTextRun(const nsAString& aString, gfxAtsuiFontGroup *aFontGroup); ~gfxAtsuiTextRun(); @@ -118,6 +122,9 @@ public: virtual void SetSpacing(const nsTArray& spacingArray); virtual const nsTArray *const GetSpacing() const; + void SetRightToLeft(PRBool aIsRTL) { mIsRTL = aIsRTL; } + PRBool IsRightToLeft() { return mIsRTL; } + private: nsString mString; gfxAtsuiFontGroup *mGroup; @@ -125,6 +132,8 @@ private: ATSUTextLayout mATSULayout; nsTArray mStylesToDispose; + + PRPackedBool mIsRTL; }; #endif /* GFX_ATSUIFONTS_H */ diff --git a/gfx/thebes/public/gfxContext.h b/gfx/thebes/public/gfxContext.h index d6352811174..9ddcb09815b 100644 --- a/gfx/thebes/public/gfxContext.h +++ b/gfx/thebes/public/gfxContext.h @@ -51,7 +51,6 @@ #include "gfxFont.h" class gfxRegion; -class gfxTextRun; /** * This is the main class for doing actual drawing. It is initialized using @@ -191,22 +190,6 @@ public: void Ellipse(gfxPoint center, gfxSize dimensions); void Polygon(const gfxPoint *points, PRUint32 numPoints); - /** - ** Text - **/ - - /** - * Add the text outline to the current path. - */ - // specify this in a sane way. - //void AddStringToPath(gfxTextRun& text); - - /** - * Draw the text run at the current point. - * XXX support drawing subsections of the text run - */ - void DrawTextRun(gfxTextRun *text, gfxPoint pt); - /** ** Transformation Matrix manipulation **/ diff --git a/gfx/thebes/public/gfxFont.h b/gfx/thebes/public/gfxFont.h index 8638d9b4f7c..fc403bb0d42 100644 --- a/gfx/thebes/public/gfxFont.h +++ b/gfx/thebes/public/gfxFont.h @@ -44,9 +44,12 @@ #include "nsString.h" #include "gfxPoint.h" #include "nsTArray.h" +#include "gfxSkipChars.h" +#include "gfxRect.h" class gfxContext; class gfxTextRun; +class nsIAtom; #define FONT_STYLE_NORMAL 0 #define FONT_STYLE_ITALIC 1 @@ -149,6 +152,7 @@ public: gfxFloat strikeoutOffset; gfxFloat underlineSize; gfxFloat underlineOffset; + gfxFloat height; gfxFloat internalLeading; gfxFloat externalLeading; @@ -173,10 +177,94 @@ protected: const gfxFontStyle *mStyle; }; +class THEBES_API gfxTextRunFactory { + THEBES_INLINE_DECL_REFCOUNTING(gfxTextRunFactory) -class THEBES_API gfxFontGroup { - THEBES_INLINE_DECL_REFCOUNTING(gfxFontGroup) +public: + // Flags >= 0x10000 are reserved for textrun clients + enum { + /** + * When set, the text string pointer used to create the text run + * is guaranteed to be available during the lifetime of the text run. + */ + TEXT_IS_PERSISTENT = 0x0001, + /** + * When set, the text is known to be all-ASCII (< 128). + */ + TEXT_IS_ASCII = 0x0002, + /** + * When set, the text is RTL. + */ + TEXT_IS_RTL = 0x0004, + /** + * When set, spacing is enabled and the textrun needs to call GetSpacing + * on the spacing provider. + */ + TEXT_ENABLE_SPACING = 0x0008, + /** + * When set, GetSpacing can return negative spacing. + */ + TEXT_ENABLE_NEGATIVE_SPACING = 0x0010, + /** + * When set, mAfter spacing for a character already includes the character + * width. Otherwise, it does not include the character width. + */ + TEXT_ABSOLUTE_SPACING = 0x0020, + /** + * When set, GetHyphenationBreaks may return true for some character + * positions, otherwise it will always return false for all characters. + */ + TEXT_ENABLE_HYPHEN_BREAKS = 0x0040, + /** + * When set, the text has no characters above 255. + */ + TEXT_IS_8BIT = 0x0080, + /** + * When set, the text may have UTF16 surrogate pairs, otherwise it + * doesn't. + */ + TEXT_HAS_SURROGATES = 0x0100 + }; + /** + * This record contains all the parameters needed to initialize a textrun. + */ + struct Parameters { + // A reference context suggesting where the textrun will be rendered + gfxContext* mContext; + // Pointer to arbitrary user data (which should outlive the textrun) + void* mUserData; + // The language of the text, or null if not known + nsIAtom* mLangGroup; + // A description of which characters have been stripped from the original + // DOM string to produce the characters in the textrun + gfxSkipChars* mSkipChars; + // A list of where linebreaks are currently placed in the textrun + PRUint32* mInitialBreaks; + PRUint32 mInitialBreakCount; + // The ratio to use to convert device pixels to application layout units + gfxFloat mPixelsToUnits; + // Flags --- see above + PRUint32 mFlags; + }; + + virtual ~gfxTextRunFactory() {} + + /** + * Create a gfxTextRun from Unicode text. The length is obtained from + * aParams->mSkipChars->GetCharCount(). + */ + virtual gfxTextRun *MakeTextRun(const PRUnichar* aString, PRUint32 aLength, + Parameters* aParams) = 0; + /** + * Create a gfxTextRun from 8-bit Unicode (UCS1?) text. The length is + * obtained from aParams->mSkipChars->GetCharCount(). + */ + virtual gfxTextRun *MakeTextRun(const PRUint8* aString, PRUint32 aLength, + Parameters* aParams) = 0; +}; + +class THEBES_API gfxFontGroup : public gfxTextRunFactory { public: gfxFontGroup(const nsAString& aFamilies, const gfxFontStyle *aStyle); @@ -198,10 +286,14 @@ public: const gfxFontStyle *GetStyle() const { return &mStyle; } - /* unicode method */ - virtual gfxTextRun *MakeTextRun(const nsAString& aString) = 0; - /* ASCII text only, not UTF-8 */ - virtual gfxTextRun *MakeTextRun(const nsACString& aString) = 0; + virtual gfxFontGroup *Copy(const gfxFontStyle *aStyle) = 0; + + // These need to be repeated from gfxTextRunFactory because of C++'s + // hiding rules! + virtual gfxTextRun *MakeTextRun(const PRUnichar* aString, PRUint32 aLength, + Parameters* aParams) = 0; + virtual gfxTextRun *MakeTextRun(const PRUint8* aString, PRUint32 aLength, + Parameters* aParams) = 0; /* helper function for splitting font families on commas and * calling a function for each family to fill the mFonts array @@ -234,27 +326,399 @@ protected: static PRBool FontResolverProc(const nsAString& aName, void *aClosure); }; - -// these do not copy the text -class THEBES_API gfxTextRun { - THEBES_INLINE_DECL_REFCOUNTING(gfxTextRun) - +/** + * gfxTextRun is an abstraction for drawing and measuring substrings of a run + * of text. + * + * \r and \n characters must be ignored completely. Substring operations + * will not normally include these characters. + * + * gfxTextRuns are not refcounted. They should be deleted when no longer required. + * + * gfxTextRuns are mostly immutable. The only things that can change are + * inter-cluster spacing and line break placement. Spacing is always obtained + * lazily by methods that need it; cached spacing is flushed via + * FlushSpacingCache(). Line breaks are stored persistently (insofar + * as they affect the shaping of glyphs; gfxTextRun does not actually do anything + * to explicitly account for line breaks). Initially there are no line breaks. + * The textrun can record line breaks before or after any given cluster. (Line + * breaks specified inside clusters are ignored.) + * + * gfxTextRuns don't need to remember their text ... often it's enough just to + * convert text to glyphs and store glyphs plus some geometry data in packed + * form. However sometimes gfxTextRuns will want to get the original text back + * to handle some unusual situation. So gfxTextRuns have two modes: "not + * remembering text" (initial state) and "remembering text". A call to + * gfxTextRun::RememberText forces a transition from the former to latter state. + * The text is never forgotten. A gfxTextRun call that receives a TextProvider + * object may call ForceRememberText to request a transition to "remembering text". + * + * It is important that zero-length substrings are handled correctly. This will + * be on the test! + */ +class THEBES_API gfxTextRun { public: - gfxTextRun() : mIsRTL(PR_FALSE) { } virtual ~gfxTextRun() {} - virtual void Draw(gfxContext *aContext, gfxPoint pt) = 0; - // returns length in pixels - virtual gfxFloat Measure(gfxContext *aContext) = 0; + enum { + // character is the start of a grapheme cluster + CLUSTER_START = 0x01, + // character is a cluster start but is part of a ligature started + // in a previous cluster. + CONTINUES_LIGATURE = 0x02, + // line break opportunity before this character + LINE_BREAK_BEFORE = 0x04 + }; + virtual void GetCharFlags(PRUint32 aStart, PRUint32 aLength, + PRUint8* aFlags) = 0; + virtual PRUint8 GetCharFlags(PRUint32 aStart) = 0; - virtual void SetSpacing(const nsTArray &spacing) = 0; - virtual const nsTArray *const GetSpacing() const = 0; + virtual PRUint32 GetLength() = 0; - // defaults to FALSE - virtual void SetRightToLeft(PRBool aIsRTL) { mIsRTL = aIsRTL; } - virtual PRBool IsRightToLeft() const { return mIsRTL; } + // All PRUint32 aStart, PRUint32 aLength ranges below are restricted to + // grapheme cluster boundaries! All offsets are in terms of the string + // passed into MakeTextRun. + + // All coordinates are in layout/app units -private: - PRBool mIsRTL; + /** + * This can be called to force gfxTextRun to remember the text used + * to create it and *never* call TextProvider::GetText again. + * + * The default implementation is to not remember anything. If you want + * to be able to recover text from the gfxTextRun user you need to override + * these. + */ + virtual void RememberText(const PRUnichar* aText, PRUint32 aLength) {} + virtual void RememberText(const PRUint8* aText, PRUint32 aLength) {} + + /** + * Set the potential linebreaks for a substring of the textrun. These are + * the "allow break before" points. Initially, there are no potential + * linebreaks. + * + * @return true if this changed the linebreaks, false if the new line + * breaks are the same as the old + */ + virtual PRBool SetPotentialLineBreaks(PRUint32 aStart, PRUint32 aLength, + PRPackedBool* aBreakBefore) = 0; + + /** + * This is provided so a textrun can (re)obtain the original text used to + * construct it, if necessary. + */ + class TextProvider { + public: + /** + * Recover the text originally used to build the textrun. This should + * only be requested infrequently as it may be slow. If you need to + * call it a lot you should probably be saving the text in the text run + * itself. It just forces the textrun user to call RememberText on the + * text run. If you call this and RememberText doesn't get called, + * then something has failed and you should handle it. + */ + virtual void ForceRememberText() = 0; + }; + + /** + * Layout provides PropertyProvider objects. These allow detection of + * potential line break points and computation of spacing. We pass the data + * this way to allow lazy data acquisition; for example BreakAndMeasureText + * will want to only ask for properties of text it's actually looking at. + * + * NOTE that requested spacing may not actually be applied, if the textrun + * is unable to apply it in some context. Exception: spacing around a + * whitespace character MUST always be applied. + */ + class PropertyProvider : public TextProvider { + public: + // Detect hyphenation break opportunities in the given range; breaks + // not at cluster boundaries will be ignored. + virtual void GetHyphenationBreaks(PRUint32 aStart, PRUint32 aLength, + PRPackedBool* aBreakBefore) = 0; + + // Returns the extra width that will be consumed by a hyphen. This should + // be constant for a given textrun. + virtual gfxFloat GetHyphenWidth() = 0; + + /** + * We let the property provider specify spacing on either side of any + * character. We need to specify both before and after + * spacing so that substring measurement can do the right things. + */ + struct Spacing { + gfxFloat mBefore; + gfxFloat mAfter; + }; + /** + * Get the spacing around the indicated characters. Spacing must be zero + * inside clusters. In other words, if character i is not + * CLUSTER_START, then character i-1 must have zero after-spacing and + * character i must have zero before-spacing. + */ + virtual void GetSpacing(PRUint32 aStart, PRUint32 aLength, + Spacing* aSpacing) = 0; + }; + + /** + * Draws a substring. Uses only GetSpacing from aBreakProvider. + * The provided point is the baseline origin on the left of the string + * for LTR, on the right of the string for RTL. + * @param aDirtyRect if non-null, drawing outside of the rectangle can be + * (but does not need to be) dropped. Note that if this is null, we cannot + * draw partial ligatures and we will assert if partial ligatures + * are detected. + * @param aAdvanceWidth if non-null, the advance width of the substring + * is returned here. + * + * Drawing should respect advance widths in the sense that for LTR runs, + * Draw(ctx, pt, offset1, length1, dirty, &provider, &advance) followed by + * Draw(ctx, gfxPoint(pt.x + advance, pt.y), offset1 + length1, length2, + * dirty, &provider, nsnull) should have the same effect as + * Draw(ctx, pt, offset1, length1+length2, dirty, &provider, nsnull). + * For RTL runs the rule is: + * Draw(ctx, pt, offset1 + length1, length2, dirty, &provider, &advance) followed by + * Draw(ctx, gfxPoint(pt.x + advance, pt.y), offset1, length1, + * dirty, &provider, nsnull) should have the same effect as + * Draw(ctx, pt, offset1, length1+length2, dirty, &provider, nsnull). + * + * Glyphs should be drawn in logical content order, which can be significant + * if they overlap (perhaps due to negative spacing). + */ + virtual void Draw(gfxContext *aContext, gfxPoint aPt, + PRUint32 aStart, PRUint32 aLength, + const gfxRect* aDirtyRect, + PropertyProvider* aProvider, + gfxFloat* aAdvanceWidth) = 0; + + /** + * Renders a substring to a path. Uses only GetSpacing from aBreakProvider. + * The provided point is the baseline origin on the left of the string + * for LTR, on the right of the string for RTL. + * @param aAdvanceWidth if non-null, the advance width of the substring + * is returned here. + * + * Drawing should respect advance widths in the way that Draw above does. + * + * Glyphs should be drawn in logical content order. + * + * UNLIKE Draw above, this cannot be used to render substrings that start or + * end inside a ligature. + */ + virtual void DrawToPath(gfxContext *aContext, gfxPoint aPt, + PRUint32 aStart, PRUint32 aLength, + PropertyProvider* aBreakProvider, + gfxFloat* aAdvanceWidth) = 0; + + /** + * Special strings are strings that we might need to draw/measure but aren't + * actually substrings of the given text. They never have extra spacing and + * aren't involved in breaking. + */ + enum SpecialString { + STRING_ELLIPSIS, + STRING_HYPHEN, + STRING_SPACE, + STRING_MAX = STRING_SPACE + }; + /** + * Draw special string. + * The provided point is the baseline origin on the left of the string + * for LTR, on the right of the string for RTL. + */ + virtual void DrawSpecialString(gfxContext *aContext, gfxPoint aPt, + SpecialString aString) = 0; + + // Metrics needed by reflow + struct Metrics { + Metrics() { + mAdvanceWidth = mAscent = mDescent = 0.0; + mBoundingBox = gfxRect(0,0,0,0); + mClusterCount = 0; + } + + void CombineWith(const Metrics& aOtherOnRight) { + mAscent = PR_MAX(mAscent, aOtherOnRight.mAscent); + mDescent = PR_MAX(mDescent, aOtherOnRight.mDescent); + mBoundingBox = + mBoundingBox.Union(aOtherOnRight.mBoundingBox + gfxPoint(mAdvanceWidth, 0)); + mAdvanceWidth += aOtherOnRight.mAdvanceWidth; + mClusterCount += aOtherOnRight.mClusterCount; + } + + // can be negative (partly due to negative spacing). + // Advance widths should be additive: the advance width of the + // (offset1, length1) plus the advance width of (offset1 + length1, + // length2) should be the advance width of (offset1, length1 + length2) + gfxFloat mAdvanceWidth; + + // For zero-width substrings, these must be zero! + gfxFloat mAscent; // always non-negative + gfxFloat mDescent; // always non-negative + + // Bounding box that is guaranteed to include everything drawn. + // If aTightBoundingBox was set to true when these metrics were + // generated, this will tightly wrap the glyphs, otherwise it is + // "loose" and may be larger than the true bounding box. + // Coordinates are relative to the baseline left origin, so typically + // mBoundingBox.y == -mAscent + gfxRect mBoundingBox; + + // Count of the number of grapheme clusters. Layout needs + // this to compute tab offsets. For SpecialStrings, this is always 1. + PRInt32 mClusterCount; + }; + + /** + * Computes the ReflowMetrics for a substring. + * Uses GetSpacing from aBreakProvider. + * @param aTightBoundingBox if true, we make the bounding box tight + */ + virtual Metrics MeasureText(PRUint32 aStart, PRUint32 aLength, + PRBool aTightBoundingBox, + PropertyProvider* aProvider) = 0; + + /** + * Computes the ReflowMetrics for a special string. + * Uses GetSpacing from aBreakProvider. + * @param aTightBoundingBox if true, we make the bounding box tight + */ + virtual Metrics MeasureTextSpecialString(SpecialString aString, + PRBool aTightBoundingBox) = 0; + + /** + * Computes just the advance width for a substring. + * Uses GetSpacing from aBreakProvider. + */ + virtual gfxFloat GetAdvanceWidth(PRUint32 aStart, PRUint32 aLength, + PropertyProvider* aProvider) = 0; + + /** + * Computes the advance width for a special string. + */ + virtual gfxFloat GetAdvanceWidthSpecialString(SpecialString aString) = 0; + + /** + * Get the font metrics that we should use for drawing text decorations. + * Overriden here so that transforming gfxTextRuns such as smallcaps + * can do something special if they want to. + */ + virtual gfxFont::Metrics GetDecorationMetrics() = 0; + + /** + * Clear all stored line breaks for the given range (both before and after), + * and then set the line-break state before aStart to aBreakBefore and + * after the last cluster to aBreakAfter. + * + * We require that before and after line breaks be consistent. For clusters + * i and i+1, we require that if there is a break after cluster i, a break + * will be specified before cluster i+1. This may be temporarily violated + * (e.g. after reflowing line L and before reflowing line L+1); to handle + * these temporary violations, we say that there is a break betwen i and i+1 + * if a break is specified after i OR a break is specified before i+1. + * + * This can change textrun geometry! The existence of a linebreak can affect + * the advance width of the cluster before the break (when kerning) or the + * geometry of one cluster before the break or any number of clusters + * after the break. (The one-cluster-before-the-break limit is somewhat + * arbitrary; if some scripts require breaking it, then we need to + * alter nsTextFrame::TrimTrailingWhitespace, perhaps drastically becase + * it could affect the layout of frames before it...) + * + * @param aAdvanceWidthDelta if non-null, returns the change in advance + * width of the given range. + */ + virtual void SetLineBreaks(PRUint32 aStart, PRUint32 aLength, + PRBool aLineBreakBefore, PRBool aLineBreakAfter, + TextProvider* aProvider, + gfxFloat* aAdvanceWidthDelta) = 0; + + /** + * Finds the longest substring that will fit into the given width. + * Uses GetHyphenationBreaks and GetSpacing from aBreakProvider. + * Guarantees the following: + * -- 0 <= result <= aMaxLength + * -- result is the maximal value of N such that either + * N < aMaxLength && line break at N && GetAdvanceWidth(aStart, N) <= aWidth + * OR N < aMaxLength && hyphen break at N && GetAdvanceWidth(aStart, N) + GetHyphenWidth() <= aWidth + * OR N == aMaxLength && GetAdvanceWidth(aStart, N) <= aWidth + * where GetAdvanceWidth assumes the effect of + * SetLineBreaks(aStart, N, aLineBreakBefore, N < aMaxLength, aProvider) + * -- if no such N exists, then result is the smallest N such that + * N < aMaxLength && line break at N + * OR N < aMaxLength && hyphen break at N + * OR N == aMaxLength + * + * The call has the effect of + * SetLineBreaks(aStart, result, aLineBreakBefore, result < aMaxLength, aProvider) + * and the returned metrics and the invariants above reflect this. + * + * @param aMaxLength this can be PR_UINT32_MAX, in which case the length used + * is up to the end of the string + * @param aLineBreakBefore set to true if and only if there is an actual + * line break at the start of this string. + * @param aSuppressInitialBreak if true, then we assume there is no possible + * linebreak before aStart. If false, then we will check the internal + * line break opportunity state before deciding whether to return 0 as the + * character to break before. + * @param aMetrics if non-null, we fill this in for the returned substring. + * If a hyphenation break was used, the hyphen is NOT included in the returned metrics. + * @param aTightBoundingBox if true, we make the bounding box in aMetrics tight + * @param aUsedHyphenation if non-null, records if we selected a hyphenation break + * @param aLastBreak if non-null and result is aMaxLength, we set this to + * the maximal N such that + * N < aMaxLength && line break at N && GetAdvanceWidth(aStart, N) <= aWidth + * OR N < aMaxLength && hyphen break at N && GetAdvanceWidth(aStart, N) + GetHyphenWidth() <= aWidth + * or PR_UINT32_MAX if no such N exists, where GetAdvanceWidth assumes + * the effect of + * SetLineBreaks(aStart, N, aLineBreakBefore, N < aMaxLength, aProvider) + * + * Note that negative advance widths are possible especially if negative + * spacing is provided. + */ + virtual PRUint32 BreakAndMeasureText(PRUint32 aStart, PRUint32 aMaxLength, + PRBool aLineBreakBefore, gfxFloat aWidth, + PropertyProvider* aProvider, + PRBool aSuppressInitialBreak, + Metrics* aMetrics, PRBool aTightBoundingBox, + PRBool* aUsedHyphenation, + PRUint32* aLastBreak) = 0; + + /** + * Update the reference context. + * XXX this is a hack. New text frame does not call this. Use only + * temporarily for old text frame. + */ + virtual void SetContext(gfxContext* aContext) {} + + /** + * Flush cached spacing data for the characters at and after aStart. + */ + virtual void FlushSpacingCache(PRUint32 aStart) = 0; + + // Utility getters + + PRBool IsRightToLeft() const { return (mFlags & gfxTextRunFactory::TEXT_IS_RTL) != 0; } + gfxFloat GetDirection() const { return (mFlags & gfxTextRunFactory::TEXT_IS_RTL) ? -1.0 : 1.0; } + void* GetUserData() const { return mUserData; } + PRUint32 GetFlags() const { return mFlags; } + const gfxSkipChars& GetSkipChars() const { return mSkipChars; } + gfxFloat GetPixelsToAppUnits() { return mPixelsToAppUnits; } + +protected: + gfxTextRun(gfxTextRunFactory::Parameters* aParams, PRBool aIs8Bit) + : mUserData(aParams->mUserData), mPixelsToAppUnits(aParams->mPixelsToUnits), + mFlags(aParams->mFlags) + { + mSkipChars.TakeFrom(aParams->mSkipChars); + if (aIs8Bit) { + mFlags |= gfxTextRunFactory::TEXT_IS_8BIT; + } + } + + void* mUserData; + gfxSkipChars mSkipChars; + gfxFloat mPixelsToAppUnits; + PRUint32 mFlags; }; #endif diff --git a/gfx/thebes/public/gfxPangoFonts.h b/gfx/thebes/public/gfxPangoFonts.h index f54a3780682..0cf707f6809 100644 --- a/gfx/thebes/public/gfxPangoFonts.h +++ b/gfx/thebes/public/gfxPangoFonts.h @@ -45,8 +45,12 @@ #include #include +//#define USE_XFT_FOR_ASCII + #include "nsDataHashtable.h" +class FontSelector; + class gfxPangoFont : public gfxFont { public: gfxPangoFont (const nsAString& aName, @@ -86,8 +90,12 @@ public: const gfxFontStyle *aStyle); virtual ~gfxPangoFontGroup (); - virtual gfxTextRun *MakeTextRun(const nsAString& aString); - virtual gfxTextRun *MakeTextRun(const nsACString& aCString); + virtual gfxFontGroup *Copy(const gfxFontStyle *aStyle); + + virtual gfxTextRun *MakeTextRun(const PRUnichar* aString, PRUint32 aLength, + Parameters* aParams); + virtual gfxTextRun *MakeTextRun(const PRUint8* aString, PRUint32 aLength, + Parameters* aParams); gfxPangoFont *GetFontAt(PRInt32 i) { return NS_STATIC_CAST(gfxPangoFont*, @@ -104,72 +112,355 @@ public: void PutCachedFont(const nsAString& aName, gfxPangoFont *aFont) { mFontCache.Put(aName, aFont); } + + struct SpecialStringData { + PangoGlyphString* mGlyphs; + gfxFloat mAdvance; + + SpecialStringData() { mGlyphs = nsnull; } + ~SpecialStringData() { if (mGlyphs) pango_glyph_string_free(mGlyphs); } + }; + SpecialStringData mSpecialStrings[gfxTextRun::STRING_MAX + 1]; + protected: static PRBool FontCallback (const nsAString& fontName, const nsACString& genericName, void *closure); + private: nsDataHashtable > mFontCache; nsTArray mAdditionalStyles; }; +#ifdef USE_XFT_FOR_ASCII + class THEBES_API gfxXftTextRun : public gfxTextRun { public: - gfxXftTextRun(const nsAString& aString, gfxPangoFontGroup *aFontGroup); - gfxXftTextRun(const nsACString& aString, gfxPangoFontGroup *aFontGroup); + gfxXftTextRun(gfxPangoFontGroup *aGroup, + const char* aString, PRInt32 aLength, PRUint32 aFlags); ~gfxXftTextRun(); - virtual void Draw(gfxContext *aContext, gfxPoint pt); - virtual gfxFloat Measure(gfxContext *aContext); - - virtual void SetSpacing(const nsTArray& spacingArray); - virtual const nsTArray *const GetSpacing() const; + virtual nsresult Init(PRBool aIsRTL, nsIAtom* aLangGroup, + gfxFloat aPixelsToUnits, + gfxSkipChars* aSkipChars, void* aUserData); + virtual void GetCharFlags(PRUint32 aStart, PRUint32 aLength, + PRUint8* aFlags); + virtual PRUint8 GetCharFlag(PRUint32 aOffset); + virtual void Draw(gfxContext* aContext, gfxPoint aPt, + PRUint32 aStart, PRUint32 aLength, + const gfxRect* aDirtyRect, + PropertyProvider* aBreakProvider, + gfxFloat* aAdvanceWidth); + virtual void Draw(gfxContext *aContext, gfxPoint aPt, + SpecialString aString); + virtual Metrics MeasureText(PRUint32 aStart, PRUint32 aLength, + PRBool aTightBoundingBox, + PropertyProvider* aBreakProvider); + virtual Metrics MeasureText(SpecialString aString, + PRBool aTightBoundingBox); + virtual gfxFloat GetAdvanceWidth(PRUint32 aStart, PRUint32 aLength, + PropertyProvider* aBreakProvider); + virtual gfxFloat GetAdvanceWidth(SpecialString aString); + virtual gfxFont::Metrics& GetDecorationMetrics(); + virtual PRUint32 BreakAndMeasureText(PRUint32 aStart, PRUint32 aMaxLength, + gfxFloat aWidth, + PropertyProvider* aBreakProvider, + PRBool aSuppressInitialBreak, + Metrics* aMetrics, PRBool aTightBoundingBox, + PRBool* aUsedHyphenation, + PRUint32* aLastBreak); + virtual void FlushSpacingCache(PRUint32 aStart, PRUint32 aLength); private: nsString mWString; nsCString mCString; - - PRBool mIsWide; - - gfxPangoFontGroup *mGroup; - - int mWidth, mHeight; - - nsTArray mSpacing; - nsTArray mUTF8Spacing; }; +#endif + struct TextSegment; class THEBES_API gfxPangoTextRun : public gfxTextRun { public: - gfxPangoTextRun(const nsAString& aString, gfxPangoFontGroup *aFontGroup); + gfxPangoTextRun(gfxPangoFontGroup *aGroup, + const PRUint8* aString, PRUint32 aLength, + gfxTextRunFactory::Parameters* aParams); + gfxPangoTextRun(gfxPangoFontGroup *aGroup, + const PRUnichar* aString, PRUint32 aLength, + gfxTextRunFactory::Parameters* aParams); ~gfxPangoTextRun(); - virtual void Draw(gfxContext *aContext, gfxPoint pt); - virtual gfxFloat Measure(gfxContext *aContext); + virtual void GetCharFlags(PRUint32 aStart, PRUint32 aLength, + PRUint8* aFlags); + 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 void DrawSpecialString(gfxContext* aContext, gfxPoint aPt, + SpecialString aString); + 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 Metrics MeasureTextSpecialString(SpecialString aString, + PRBool aTightBoundingBox); + virtual gfxFloat GetAdvanceWidth(PRUint32 aStart, PRUint32 aLength, + PropertyProvider* aBreakProvider); + virtual gfxFloat GetAdvanceWidthSpecialString(SpecialString aString); + virtual gfxFont::Metrics GetDecorationMetrics(); + virtual PRUint32 BreakAndMeasureText(PRUint32 aStart, PRUint32 aMaxLength, + PRBool aLineBreakBefore, gfxFloat aWidth, + PropertyProvider* aBreakProvider, + PRBool aSuppressInitialBreak, + Metrics* aMetrics, PRBool aTightBoundingBox, + PRBool* aUsedHyphenation, + PRUint32* aLastBreak); + virtual void FlushSpacingCache(PRUint32 aStart); - virtual void SetSpacing(const nsTArray& spacingArray); - virtual const nsTArray *const GetSpacing() const; + /** + * This class records the information associated with a character in the + * input string. It's optimized for the case where there is one glyph + * representing that character alone. + */ + class CompressedGlyph { + public: + CompressedGlyph() { mValue = 0; } + + enum { + // Indicates that a cluster starts at this character and can be + // rendered using a single glyph with a reasonable advance offset + // and no special glyph offset. A "reasonable" advance offset is + // one that is a) a multiple of a pixel and b) fits in the available + // bits (currently 14). We should revisit this, especially a), + // if we want to support subpixel-aligned text. + FLAG_IS_SIMPLE_GLYPH = 0x80000000U, + // Indicates that a linebreak is allowed before this character + FLAG_CAN_BREAK_BEFORE = 0x40000000U, + + ADVANCE_MASK = 0x3FFF0000U, + ADVANCE_SHIFT = 16, + + GLYPH_MASK = 0x0000FFFFU, + + // Non-simple glyphs have the following tags + + TAG_MASK = 0x000000FFU, + // Indicates that a cluster starts at this character and is rendered + // using one or more glyphs which cannot be represented here. + // Look up the DetailedGlyph table instead. + TAG_COMPLEX_CLUSTER = 0x00U, + // Indicates that a cluster starts at this character but is rendered + // as part of a ligature starting in a previous cluster. + // NOTE: we divide up the ligature's width by the number of clusters + // to get the width assigned to each cluster. + TAG_LIGATURE_CONTINUATION = 0x01U, + + // Values where the upper 28 bits equal 0x80 are reserved for + // non-cluster-start characters (see IsClusterStart below) + + // Indicates that a cluster does not start at this character, this is + // a low UTF16 surrogate + TAG_LOW_SURROGATE = 0x80U, + // Indicates that a cluster does not start at this character, this is + // part of a cluster starting with an earlier character (but not + // a low surrogate). + TAG_CLUSTER_CONTINUATION = 0x81U + }; + + // Returns true if the glyph ID aGlyph fits into the compressed representation + static PRBool IsSimpleGlyphID(PRUint32 aGlyph) { + return (aGlyph & GLYPH_MASK) == aGlyph; + } + // Returns true if the advance aAdvance fits into the compressed representation + static PRBool IsSimpleAdvance(PRUint32 aAdvance) { + return (aAdvance & (ADVANCE_MASK >> ADVANCE_SHIFT)) == aAdvance; + } + + PRBool IsSimpleGlyph() { return (mValue & FLAG_IS_SIMPLE_GLYPH) != 0; } + PRBool IsComplex(PRUint32 aTag) { return (mValue & (FLAG_IS_SIMPLE_GLYPH|TAG_MASK)) == aTag; } + PRBool IsComplexCluster() { return IsComplex(TAG_COMPLEX_CLUSTER); } + PRBool IsLigatureContinuation() { return IsComplex(TAG_LIGATURE_CONTINUATION); } + PRBool IsLowSurrogate() { return IsComplex(TAG_LOW_SURROGATE); } + PRBool IsClusterStart() { return (mValue & (FLAG_IS_SIMPLE_GLYPH|0x80U)) != 0x80U; } + + PRUint32 GetSimpleAdvance() { return (mValue & ADVANCE_MASK) >> ADVANCE_SHIFT; } + PRUint32 GetSimpleGlyph() { return mValue & GLYPH_MASK; } + + PRUint32 GetComplexTag() { return mValue & TAG_MASK; } + + PRBool CanBreakBefore() { return (mValue & FLAG_CAN_BREAK_BEFORE) != 0; } + // Returns FLAG_CAN_BREAK_BEFORE if the setting changed, 0 otherwise + PRUint32 SetCanBreakBefore(PRBool aCanBreakBefore) { + PRUint32 breakMask = aCanBreakBefore*FLAG_CAN_BREAK_BEFORE; + PRUint32 toggle = breakMask ^ (mValue & FLAG_CAN_BREAK_BEFORE); + mValue ^= toggle; + return toggle; + } + + void SetSimpleGlyph(PRUint32 aAdvance, PRUint32 aGlyph) { + NS_ASSERTION(IsSimpleAdvance(aAdvance), "Advance overflow"); + NS_ASSERTION(IsSimpleGlyphID(aGlyph), "Glyph overflow"); + mValue = (mValue & FLAG_CAN_BREAK_BEFORE) | FLAG_IS_SIMPLE_GLYPH | + (aAdvance << ADVANCE_SHIFT) | aGlyph; + } + void SetComplex(PRUint32 aTag) { mValue = (mValue & FLAG_CAN_BREAK_BEFORE) | aTag; } + void SetComplexCluster() { SetComplex(TAG_COMPLEX_CLUSTER); } + void SetLowSurrogate() { SetComplex(TAG_LOW_SURROGATE); } + void SetLigatureContinuation() { SetComplex(TAG_LIGATURE_CONTINUATION); } + void SetClusterContinuation() { SetComplex(TAG_CLUSTER_CONTINUATION); } + private: + PRUint32 mValue; + }; + struct DetailedGlyph { + PRUint32 mIsLastGlyph:1; + // mGlyphID == 2^31 means "missing glyph" + PRUint32 mGlyphID:31; + float mAdvance, mXOffset, mYOffset; + + // This is the ID of a missing glyph in a details record. All "missing" glyphs + // are converted to a detailed glyph record with this glyph ID. + enum { + DETAILED_MISSING_GLYPH = 1U << 30 + }; + }; + // The text is divided into GlyphRuns at Pango item and text segment boundaries + struct GlyphRun { + PangoFont* mPangoFont; // strong ref; can't be null + cairo_scaled_font_t* mCairoFont; // could be null + PRUint32 mCharacterOffset; // into original UTF16 string + + ~GlyphRun() { + if (mCairoFont) { + cairo_scaled_font_destroy(mCairoFont); + } + if (mPangoFont) { + g_object_unref(mPangoFont); + } + } + }; + + class GlyphRunIterator { + public: + GlyphRunIterator(gfxPangoTextRun* aTextRun, PRUint32 aStart, PRUint32 aLength) + : mTextRun(aTextRun), mStartOffset(aStart), mEndOffset(aStart + aLength) { + mNextIndex = mTextRun->FindFirstGlyphRunContaining(aStart); + } + PRBool NextRun(); + GlyphRun* GetGlyphRun() { return mGlyphRun; } + PRUint32 GetStringStart() { return mStringStart; } + PRUint32 GetStringEnd() { return mStringEnd; } + private: + gfxPangoTextRun* mTextRun; + GlyphRun* mGlyphRun; + PRUint32 mStringStart; + PRUint32 mStringEnd; + PRUint32 mNextIndex; + PRUint32 mStartOffset; + PRUint32 mEndOffset; + }; + + friend class GlyphRunIterator; + friend class FontSelector; + + // **** Initialization helpers **** private: - nsString mString; - nsCString mUTF8String; + // If aUTF16Text is null, then the string contains no characters >= 0x100 + void Init(gfxTextRunFactory::Parameters* aParams, const gchar* aUTF8Text, + PRUint32 aUTF8Length, PRUint32 aUTF8HeaderLength, const PRUnichar* aUTF16Text, + PRUint32 aUTF16Length); + void SetupClusterBoundaries(const gchar* aUTF8, PRUint32 aUTF8Length, + PRUint32 aUTF16Offset, PangoAnalysis* aAnalysis); + nsresult AddGlyphRun(PangoFont* aFont, PRUint32 aUTF16Offset); + // Returns NS_ERROR_FAILURE if there's a missing glyph + nsresult SetGlyphs(const gchar* aUTF8, PRUint32 aUTF8Length, + PRUint32* aUTF16Offset, PangoGlyphString* aGlyphs, + PangoGlyphUnit aOverrideSpaceWidth, + PRBool aAbortOnMissingGlyph); + // If aUTF16Text is null, then the string contains no characters >= 0x100. + // Returns NS_ERROR_FAILURE if we require the itemizing path + nsresult CreateGlyphRunsFast(const gchar* aUTF8, PRUint32 aUTF8Length, + const PRUnichar* aUTF16Text, PRUint32 aUTF16Length); + void CreateGlyphRunsItemizing(const gchar* aUTF8, PRUint32 aUTF8Length, + PRUint32 aUTF8HeaderLength); - gfxPangoFontGroup *mGroup; + // **** general helpers **** - PangoLayout *mPangoLayout; - int mWidth; + void SetupPangoContextDirection(); + static void SetupCairoFont(cairo_t* aCR, GlyphRun* aGlyphRun); + // Returns the index of the GlyphRun containing the given offset. + // Returns mGlyphRuns.Length() when aOffset is mCharacterCount. + PRUint32 FindFirstGlyphRunContaining(PRUint32 aOffset); + // Computes the x-advance for a given cluster starting at aClusterOffset. Does + // not include any spacing. Result is in device pixels. + gfxFloat ComputeClusterAdvance(PRUint32 aClusterOffset); - int MeasureOrDraw(gfxContext *aContext, PRBool aDraw, gfxPoint aPt); - int MeasureOrDrawItemizing(gfxContext *aContext, PRBool aDraw, - gfxPoint aPt, PRBool aIsRTL, - gfxPangoFont *aFont); - int DrawFromCache(gfxContext *aContext, gfxPoint aPt); + // **** ligature helpers **** + // (Pango does the actual ligaturization, but we need to do a bunch of stuff + // to handle requests that begin or end inside a ligature) - nsTArray mSpacing; - nsTArray mUTF8Spacing; - nsTArray mSegments; + struct LigatureData { + PRUint32 mStartOffset; + PRUint32 mEndOffset; + PRUint32 mClusterCount; + PRUint32 mPartClusterIndex; + gfxFloat mLigatureWidth; // appunits + gfxFloat mBeforeSpacing; // appunits + gfxFloat mAfterSpacing; // appunits + }; + // if aProvider is null then mBeforeSpacing and mAfterSpacing are set to zero + LigatureData ComputeLigatureData(PRUint32 aPartOffset, PropertyProvider* aProvider); + void GetAdjustedSpacing(PRUint32 aStart, PRUint32 aEnd, + PropertyProvider* aProvider, PropertyProvider::Spacing* aSpacing); + PRBool GetAdjustedSpacingArray(PRUint32 aStart, PRUint32 aEnd, + PropertyProvider* aProvider, + nsTArray* aSpacing); + void DrawPartialLigature(gfxContext* aCtx, PRUint32 aOffset, + const gfxRect* aDirtyRect, gfxPoint* aPt, + PropertyProvider* aProvider); + // result in appunits + void ShrinkToLigatureBoundaries(PRUint32* aStart, PRUint32* aEnd); + gfxFloat GetPartialLigatureWidth(PRUint32 aStart, PRUint32 aEnd, PropertyProvider* aProvider); + void AccumulatePartialLigatureMetrics(PangoFont* aPangoFont, + PRUint32 aOffset, PropertyProvider* aProvider, + Metrics* aMetrics); + + // **** measurement helper **** + void AccumulatePangoMetricsForRun(PangoFont* aPangoFont, PRUint32 aStart, + PRUint32 aEnd, PropertyProvider* aProvider, + Metrics* aMetrics); + + // **** drawing helpers **** + + typedef void (* CairoGlyphProcessorCallback) + (void* aClosure, cairo_glyph_t* aGlyphs, int aNumGlyphs); + void ProcessCairoGlyphsWithSpacing(CairoGlyphProcessorCallback aCB, void* aClosure, + gfxPoint* aPt, PRUint32 aStart, PRUint32 aEnd, + PropertyProvider::Spacing* aSpacing); + void ProcessCairoGlyphs(CairoGlyphProcessorCallback aCB, void* aClosure, + gfxPoint* aPt, PRUint32 aStart, PRUint32 aEnd, + PropertyProvider* aProvider); + static void CairoShowGlyphs(void* aClosure, cairo_glyph_t* aGlyphs, int aNumGlyphs); + static void CairoGlyphsToPath(void* aClosure, cairo_glyph_t* aGlyphs, int aNumGlyphs); + + nsRefPtr mFontGroup; + // All our glyph data is in logical order, not visual + nsAutoArrayPtr mCharacterGlyphs; + nsAutoArrayPtr > mDetailedGlyphs; // only non-null if needed + nsAutoTArray mGlyphRuns; + PRUint32 mCharacterCount; }; #endif /* GFX_PANGOFONTS_H */ diff --git a/gfx/thebes/public/gfxTextRunCache.h b/gfx/thebes/public/gfxTextRunCache.h index 2c2060e1e71..055b9d1fad4 100644 --- a/gfx/thebes/public/gfxTextRunCache.h +++ b/gfx/thebes/public/gfxTextRunCache.h @@ -58,15 +58,17 @@ public: static nsresult Init(); static void Shutdown(); - /* Will return a pointer to a gfxTextRun, either from the cache - * or a newly created (and cached) run. - * - * The caller does not have to addref the result, as long as - * it is not used past another call to GetOrMakeTextRun, at which point - * it may be evicted. + /* Will return a pointer to a gfxTextRun, which may or may not be from + * the cache. If aCallerOwns is set to true, the caller owns the textrun + * and must delete it. Otherwise the returned textrun is only valid until + * the next GetOrMakeTextRun call and the caller must not delete it. */ - gfxTextRun *GetOrMakeTextRun (gfxFontGroup *aFontGroup, const nsAString& aString); - gfxTextRun *GetOrMakeTextRun (gfxFontGroup *aFontGroup, const nsACString& aString); + gfxTextRun *GetOrMakeTextRun (gfxContext* aContext, gfxFontGroup *aFontGroup, + const char *aString, PRUint32 aLength, gfxFloat aDevToApp, + PRBool aIsRTL, PRBool aEnableSpacing, PRBool *aCallerOwns); + gfxTextRun *GetOrMakeTextRun (gfxContext* aContext, gfxFontGroup *aFontGroup, + const PRUnichar *aString, PRUint32 aLength, gfxFloat aDevToApp, + PRBool aIsRTL, PRBool aEnableSpacing, PRBool *aCallerOwns); protected: gfxTextRunCache(); @@ -146,8 +148,10 @@ protected: TextRunEntry(gfxTextRun *tr) : textRun(tr), lastUse(PR_Now()) { } void Used() { lastUse = PR_Now(); } - nsRefPtr textRun; - PRTime lastUse; + gfxTextRun* textRun; + PRTime lastUse; + + ~TextRunEntry() { delete textRun; } }; typedef FontGroupAndStringT FontGroupAndString; @@ -156,9 +160,6 @@ protected: typedef FontGroupAndStringHashKeyT FontGroupAndStringHashKey; typedef FontGroupAndStringHashKeyT FontGroupAndCStringHashKey; - //nsRefPtrHashtable mHashTableUTF16; - //nsRefPtrHashtable mHashTableASCII; - nsClassHashtable mHashTableUTF16; nsClassHashtable mHashTableASCII; diff --git a/gfx/thebes/public/gfxWindowsFonts.h b/gfx/thebes/public/gfxWindowsFonts.h index 39d78923731..d67783c995d 100644 --- a/gfx/thebes/public/gfxWindowsFonts.h +++ b/gfx/thebes/public/gfxWindowsFonts.h @@ -420,8 +420,14 @@ public: gfxWindowsFontGroup(const nsAString& aFamilies, const gfxFontStyle* aStyle); virtual ~gfxWindowsFontGroup(); - virtual gfxTextRun *MakeTextRun(const nsAString& aString); - virtual gfxTextRun *MakeTextRun(const nsACString& aString); + virtual gfxFontGroup *Copy(const gfxFontStyle *aStyle) { + NS_ERROR("NOT IMPLEMENTED"); + return nsnull; + } + virtual gfxTextRun *MakeTextRun(const PRUnichar* aString, PRUint32 aLength, + Parameters* aParams); + virtual gfxTextRun *MakeTextRun(const PRUint8* aString, PRUint32 aLength, + Parameters* aParams); const nsACString& GetGenericFamily() const { return mGenericFamily; @@ -478,7 +484,7 @@ private: * **********************************************************************/ -class THEBES_API gfxWindowsTextRun : public gfxTextRun { +class THEBES_API gfxWindowsTextRun { public: gfxWindowsTextRun(const nsAString& aString, gfxWindowsFontGroup *aFontGroup); gfxWindowsTextRun(const nsACString& aString, gfxWindowsFontGroup *aFontGroup); @@ -489,6 +495,9 @@ public: virtual void SetSpacing(const nsTArray& spacingArray); virtual const nsTArray *const GetSpacing() const; + + void SetRightToLeft(PRBool aIsRTL) { mIsRTL = aIsRTL; } + PRBool IsRightToLeft() { return mIsRTL; } private: double MeasureOrDrawFast(gfxContext *aContext, PRBool aDraw, gfxPoint pt); @@ -502,8 +511,9 @@ private: nsString mString; nsCString mCString; - const PRBool mIsASCII; - + const PRPackedBool mIsASCII; + PRPackedBool mIsRTL; + nsRefPtr mFallbackFont; /* cached values */ diff --git a/gfx/thebes/src/gfxAtsuiFonts.cpp b/gfx/thebes/src/gfxAtsuiFonts.cpp index 72cfe1ce133..6398495f15c 100644 --- a/gfx/thebes/src/gfxAtsuiFonts.cpp +++ b/gfx/thebes/src/gfxAtsuiFonts.cpp @@ -315,10 +315,156 @@ gfxAtsuiFontGroup::~gfxAtsuiFontGroup() ATSUDisposeFontFallbacks(mFallbacks); } -gfxTextRun* -gfxAtsuiFontGroup::MakeTextRun(const nsAString& aString) +class gfxWrapperTextRun : public gfxTextRun { +public: + gfxWrapperTextRun(gfxAtsuiFontGroup *aGroup, + const PRUint8* aString, PRUint32 aLength, + gfxTextRunFactory::Parameters* aParams) + : gfxTextRun(aParams, PR_TRUE), + mContext(aParams->mContext), + mInner(NS_ConvertASCIItoUTF16(nsDependentCSubstring(reinterpret_cast(aString), + reinterpret_cast(aString + aLength))), + aGroup), + mLength(aLength) + { + mInner.SetRightToLeft(IsRightToLeft()); + } + gfxWrapperTextRun(gfxAtsuiFontGroup *aGroup, + const PRUnichar* aString, PRUint32 aLength, + gfxTextRunFactory::Parameters* aParams) + : gfxTextRun(aParams, PR_TRUE), mContext(aParams->mContext), + mInner(nsDependentSubstring(aString, aString + aLength), aGroup), + mLength(aLength) + { + mInner.SetRightToLeft(IsRightToLeft()); + } + ~gfxWrapperTextRun() {} + + virtual void GetCharFlags(PRUint32 aStart, PRUint32 aLength, + PRUint8* aFlags) + { NS_ERROR("NOT IMPLEMENTED"); } + virtual PRUint8 GetCharFlags(PRUint32 aOffset) + { NS_ERROR("NOT IMPLEMENTED"); return 0; } + virtual PRUint32 GetLength() + { NS_ERROR("NOT IMPLEMENTED"); return 0; } + virtual PRBool SetPotentialLineBreaks(PRUint32 aStart, PRUint32 aLength, + PRPackedBool* aBreakBefore) + { NS_ERROR("NOT IMPLEMENTED"); return PR_FALSE; } + virtual void DrawToPath(gfxContext *aContext, gfxPoint aPt, + PRUint32 aStart, PRUint32 aLength, + PropertyProvider* aBreakProvider, + gfxFloat* aAdvanceWidth) + { NS_ERROR("NOT IMPLEMENTED"); } + virtual void DrawSpecialString(gfxContext* aContext, gfxPoint aPt, + SpecialString aString) + { NS_ERROR("NOT IMPLEMENTED"); } + virtual Metrics MeasureText(PRUint32 aStart, PRUint32 aLength, + PRBool aTightBoundingBox, + PropertyProvider* aBreakProvider) + { NS_ERROR("NOT IMPLEMENTED"); return Metrics(); } + virtual Metrics MeasureTextSpecialString(SpecialString aString, + PRBool aTightBoundingBox) + { NS_ERROR("NOT IMPLEMENTED"); return Metrics(); } + virtual gfxFloat GetAdvanceWidthSpecialString(SpecialString aString) + { NS_ERROR("NOT IMPLEMENTED"); return 0; } + virtual gfxFont::Metrics GetDecorationMetrics() + { NS_ERROR("NOT IMPLEMENTED"); return gfxFont::Metrics(); } + virtual void SetLineBreaks(PRUint32 aStart, PRUint32 aLength, + PRBool aLineBreakBefore, PRBool aLineBreakAfter, + TextProvider* aProvider, + gfxFloat* aAdvanceWidthDelta) + { NS_ERROR("NOT IMPLEMENTED"); } + virtual PRUint32 BreakAndMeasureText(PRUint32 aStart, PRUint32 aMaxLength, + PRBool aLineBreakBefore, gfxFloat aWidth, + PropertyProvider* aProvider, + PRBool aSuppressInitialBreak, + Metrics* aMetrics, PRBool aTightBoundingBox, + PRBool* aUsedHyphenation, + PRUint32* aLastBreak) + { NS_ERROR("NOT IMPLEMENTED"); return 0; } + virtual void FlushSpacingCache(PRUint32 aStart) + { NS_ERROR("NOT IMPLEMENTED"); } + + virtual void Draw(gfxContext *aContext, gfxPoint aPt, + PRUint32 aStart, PRUint32 aLength, + const gfxRect* aDirtyRect, + PropertyProvider* aBreakProvider, + gfxFloat* aAdvanceWidth); + virtual gfxFloat GetAdvanceWidth(PRUint32 aStart, PRUint32 aLength, + PropertyProvider* aBreakProvider); + + virtual void SetContext(gfxContext* aContext) { mContext = aContext; } + +private: + gfxContext* mContext; + gfxAtsuiTextRun mInner; + PRUint32 mLength; + + void SetupSpacingFromProvider(PropertyProvider* aProvider); +}; + +#define ROUND(x) floor((x) + 0.5) + +void +gfxWrapperTextRun::SetupSpacingFromProvider(PropertyProvider* aProvider) { - return new gfxAtsuiTextRun(aString, this); + if (!(mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING)) + return; + + NS_ASSERTION(mFlags & gfxTextRunFactory::TEXT_ABSOLUTE_SPACING, + "Can't handle relative spacing"); + + nsAutoTArray spacing; + spacing.AppendElements(mLength); + aProvider->GetSpacing(0, mLength, spacing.Elements()); + + nsTArray spaceArray; + PRUint32 i; + gfxFloat offset = 0; + for (i = 0; i < mLength; ++i) { + NS_ASSERTION(spacing.Elements()[i].mBefore == 0, + "Can't handle before-spacing!"); + gfxFloat nextOffset = offset + spacing.Elements()[i].mAfter/mPixelsToAppUnits; + spaceArray.AppendElement(ROUND(nextOffset) - ROUND(offset)); + offset = nextOffset; + } + mInner.SetSpacing(spaceArray); +} + +void +gfxWrapperTextRun::Draw(gfxContext *aContext, gfxPoint aPt, + PRUint32 aStart, PRUint32 aLength, + const gfxRect* aDirtyRect, + PropertyProvider* aBreakProvider, + gfxFloat* aAdvanceWidth) +{ + NS_ASSERTION(aStart == 0 && aLength == mLength, "Can't handle substrings"); + SetupSpacingFromProvider(aBreakProvider); + gfxPoint pt(aPt.x/mPixelsToAppUnits, aPt.y/mPixelsToAppUnits); + return mInner.Draw(mContext, pt); +} + +gfxFloat +gfxWrapperTextRun::GetAdvanceWidth(PRUint32 aStart, PRUint32 aLength, + PropertyProvider* aBreakProvider) +{ + NS_ASSERTION(aStart == 0 && aLength == mLength, "Can't handle substrings"); + SetupSpacingFromProvider(aBreakProvider); + return mInner.Measure(mContext)*mPixelsToAppUnits; +} + +gfxTextRun * +gfxAtsuiFontGroup::MakeTextRun(const PRUnichar* aString, PRUint32 aLength, + Parameters* aParams) +{ + return new gfxWrapperTextRun(this, aString, aLength, aParams); +} + +gfxTextRun * +gfxAtsuiFontGroup::MakeTextRun(const PRUint8* aString, PRUint32 aLength, + Parameters* aParams) +{ + return new gfxWrapperTextRun(this, aString, aLength, aParams); } gfxAtsuiFont* diff --git a/gfx/thebes/src/gfxContext.cpp b/gfx/thebes/src/gfxContext.cpp index 5d869596614..98f2eae2467 100644 --- a/gfx/thebes/src/gfxContext.cpp +++ b/gfx/thebes/src/gfxContext.cpp @@ -649,14 +649,6 @@ gfxContext::Mask(gfxASurface *surface, gfxPoint offset) cairo_mask_surface(mCairo, surface->CairoSurface(), offset.x, offset.y); } -// fonts? -void -gfxContext::DrawTextRun(gfxTextRun *text, gfxPoint pt) -{ - if (text) - text->Draw(this, pt); -} - void gfxContext::Paint(gfxFloat alpha) { diff --git a/gfx/thebes/src/gfxPangoFonts.cpp b/gfx/thebes/src/gfxPangoFonts.cpp index d646ccb52fa..fc910a56f9c 100644 --- a/gfx/thebes/src/gfxPangoFonts.cpp +++ b/gfx/thebes/src/gfxPangoFonts.cpp @@ -58,10 +58,10 @@ #include "nsIPrefBranch.h" #include "nsIPrefService.h" #include "nsServiceManagerUtils.h" +#include "nsUnitConversion.h" #include "nsVoidArray.h" #include "nsPromiseFlatString.h" -#include "nsUTF8Utils.h" #include "gfxContext.h" #include "gfxPangoFonts.h" @@ -160,49 +160,29 @@ gfxPangoFontGroup::~gfxPangoFontGroup() { } - -static PRBool -IsOutsideASCII(const nsAString& aName) +gfxFontGroup* +gfxPangoFontGroup::Copy(const gfxFontStyle* aStyle) { - PRUint32 len = aName.Length(); - const PRUnichar* str = aName.BeginReading(); - for (PRUint32 i = 0; i < len; i++) { - /* - * is outside 7 bit ASCII - */ - if (str[i] > 0x7E) { - return PR_TRUE; - } - } - - return PR_FALSE; -} - -#define USE_XFT_FOR_ASCII - -gfxTextRun* -gfxPangoFontGroup::MakeTextRun(const nsAString& aString) -{ -#ifdef USE_XFT_FOR_ASCII - if (IsOutsideASCII(aString)) - return new gfxPangoTextRun(aString, this); - - return new gfxXftTextRun(aString, this); -#else - return new gfxPangoTextRun(aString, this); -#endif + return new gfxPangoFontGroup(mFamilies, aStyle); } gfxTextRun* -gfxPangoFontGroup::MakeTextRun(const nsACString& aString) +gfxPangoFontGroup::MakeTextRun(const PRUnichar* aString, PRUint32 aLength, + Parameters* aParams) { -#ifdef USE_XFT_FOR_ASCII - return new gfxXftTextRun(aString, this); -#else - return new gfxPangoTextRun(NS_ConvertASCIItoUTF16(aString), this); -#endif + return new gfxPangoTextRun(this, aString, aLength, aParams); } +gfxTextRun* +gfxPangoFontGroup::MakeTextRun(const PRUint8* aString, PRUint32 aLength, + Parameters* aParams) +{ +#ifdef USE_XFT_FOR_ASCII + return new gfxXftTextRun(aString, aLength, aPersistentString); +#else + return new gfxPangoTextRun(this, aString, aLength, aParams); +#endif +} /** ** gfxPangoFont @@ -651,10 +631,10 @@ GetCJKLangGroupIndex(const char *aLangGroup) /** ** gfxXftTextRun **/ +#ifdef USE_XFT_FOR_ASCII gfxXftTextRun::gfxXftTextRun(const nsAString& aString, gfxPangoFontGroup *aGroup) : mWString(aString), mIsWide(PR_TRUE), mGroup(aGroup), mWidth(-1), mHeight(-1) - { } @@ -665,15 +645,99 @@ gfxXftTextRun::gfxXftTextRun(const nsACString& aString, gfxPangoFontGroup *aGrou gfxXftTextRun::~gfxXftTextRun() { + if (mOwnsText) { + delete[] mText; + } } -#define AUTO_GLYPHBUF_SIZE 100 +nsresult +gfxXftTextRun::Init(PRBool aIsRTL, nsIAtom* aLangGroup, + gfxFloat aPixelsToUnits, + gfxSkipChars* aSkipChars, void* aUserData) +{ + nsresult rv = gfxTextRun::Init(aIsRTL, aLangGroup, aPixelsToUnits, + aSkipChars, aUserData); + if (NS_FAILED(rv)) + return rv; + if (!mText) + return NS_ERROR_OUT_OF_MEMORY; + return NS_OK; +} + +void +gfxXftTextRun::GetCharFlags(PRUint32 aStart, PRUint32 aLength, + PRUint8* aFlags) +{ +} + +PRUint8 +gfxXftTextRun::GetCharFlag(PRUint32 aOffset) +{ +} + +void +gfxXftTextRun::Draw(gfxContext* aContext, gfxPoint aPt, + PRUint32 aStart, PRUint32 aLength, + gfxRect aDirtyRect, + PropertyProvider* aBreakProvider, + gfxFloat* aAdvanceWidth) +{ +} + +void +gfxXftTextRun::Draw(gfxContext *aContext, gfxPoint aPt, + SpecialString aString) +{ +} + +gfxTextRun::Metrics +gfxXftTextRun::MeasureText(PRUint32 aStart, PRUint32 aLength, + PRBool aTightBoundingBox, + PropertyProvider* aBreakProvider) +{ +} + +gfxTextRun::Metrics +gfxXftTextRun::MeasureText(SpecialString aString, + PRBool aTightBoundingBox) +{ +} + +gfxFloat +gfxXftTextRun::GetAdvanceWidth(PRUint32 aStart, PRUint32 aLength, + PropertyProvider* aBreakProvider) +{ +} + +gfxFloat +gfxXftTextRun::GetAdvanceWidth(SpecialString aString) +{ +} + +const gfxFont::Metrics& +gfxXftTextRun::GetDecorationMetrics() +{ +} + +PRUint32 +gfxXftTextRun::BreakAndMeasureText(PRUint32 aStart, PRUint32 aMaxLength, + gfxFloat aWidth, + PropertyProvider* aBreakProvider, + PRBool aSuppressInitialBreak, + Metrics* aMetrics, PRBool aTightBoundingBox, + PRBool* aUsedHyphenation, + PRUint32* aLastBreak) +{ +} + +void +gfxXftTextRun::FlushSpacingCache(PRUint32 aStart, PRUint32 aLength) +{ +} void gfxXftTextRun::Draw(gfxContext *aContext, gfxPoint pt) { - gfxMatrix mat = aContext->CurrentMatrix(); - nsRefPtr pf = mGroup->GetFontAt(0); //printf("2. %s\n", nsPromiseFlatCString(mString).get()); XftFont * xfont = pf->GetXftFont(); @@ -709,10 +773,10 @@ gfxXftTextRun::Draw(gfxContext *aContext, gfxPoint pt) glyphs[i].x = pt.x + offset; glyphs[i].y = pt.y; - XGlyphInfo info; + XGlyphInfo info; XftGlyphExtents(GDK_DISPLAY(), xfont, &glyph, 1, &info); offset += !mUTF8Spacing.IsEmpty() ? mUTF8Spacing[i] : info.xOff; - } + } cairo_show_glyphs(aContext->GetCairo(), glyphs, len); @@ -728,8 +792,6 @@ gfxXftTextRun::Draw(gfxContext *aContext, gfxPoint pt) */ cairo_font_face_destroy(font); - - //aContext->SetMatrix(mat); } gfxFloat @@ -792,196 +854,1592 @@ gfxXftTextRun::GetSpacing() const return &mSpacing; } - +#endif /** ** gfxPangoTextRun + * + * Some serious problems: + * + * -- We draw with a font that's hinted for the CTM, but we measure with a font + * hinted to the identity matrix, so our "bounding metrics" may not be accurate. + * + * -- CreateScaledFont doesn't necessarily give us the font that the Pango + * metrics assume. + * **/ -#ifndef THEBES_USE_PANGO_CAIRO +PRBool +gfxPangoTextRun::GlyphRunIterator::NextRun() { + if (mNextIndex >= mTextRun->mGlyphRuns.Length()) + return PR_FALSE; + mGlyphRun = &mTextRun->mGlyphRuns[mNextIndex]; + if (mGlyphRun->mCharacterOffset >= mEndOffset) + return PR_FALSE; -#define AUTO_GLYPHBUF_SIZE 100 + mStringStart = PR_MAX(mStartOffset, mGlyphRun->mCharacterOffset); + PRUint32 last = mNextIndex + 1 < mTextRun->mGlyphRuns.Length() + ? mTextRun->mGlyphRuns[mNextIndex + 1].mCharacterOffset : mTextRun->mCharacterCount; + mStringEnd = PR_MIN(mEndOffset, last); -static gint -DrawCairoGlyphs(gfxContext* ctx, - PangoFont* aFont, - const gfxPoint& pt, - PangoGlyphString* aGlyphs) + ++mNextIndex; + return PR_TRUE; +} + +/** + * We use this to append an LTR or RTL Override character to the start of the + * string. This forces Pango to honour our direction even if there are neutral characters + * in the string. + */ +static PRInt32 AppendDirectionalIndicatorUTF8(PRBool aIsRTL, nsACString& aString) { - PangoFcFont* fcfont = PANGO_FC_FONT(aFont); - cairo_font_face_t* font = cairo_ft_font_face_create_for_pattern(fcfont->font_pattern); - cairo_set_font_face(ctx->GetCairo(), font); + static const PRUnichar overrides[2][2] = + { { 0x202d, 0 }, { 0x202e, 0 }}; // LRO, RLO + AppendUTF16toUTF8(overrides[aIsRTL], aString); + return 3; // both overrides map to 3 bytes in UTF8 +} - double size; - if (FcPatternGetDouble(fcfont->font_pattern, FC_PIXEL_SIZE, 0, &size) != FcResultMatch) - size = 12.0; +gfxPangoTextRun::gfxPangoTextRun(gfxPangoFontGroup *aGroup, + const PRUint8* aString, PRUint32 aLength, + gfxTextRunFactory::Parameters* aParams) + : gfxTextRun(aParams, PR_TRUE), mFontGroup(aGroup), mCharacterCount(aLength) +{ + mCharacterGlyphs = new CompressedGlyph[aLength]; + if (!mCharacterGlyphs) + return; - cairo_set_font_size(ctx->GetCairo(), size); + const gchar* utf8Chars = NS_REINTERPRET_CAST(const gchar*, aString); - cairo_glyph_t autoGlyphs[AUTO_GLYPHBUF_SIZE]; - cairo_glyph_t* glyphs = autoGlyphs; - if (aGlyphs->num_glyphs > AUTO_GLYPHBUF_SIZE) - glyphs = new cairo_glyph_t[aGlyphs->num_glyphs]; + PRBool isRTL = IsRightToLeft(); + if ((mFlags & gfxTextRunFactory::TEXT_IS_ASCII) && !isRTL) { + // We don't need to send an override character here, the characters must be all + // LTR + Init(aParams, utf8Chars, aLength, 0, nsnull, 0); + } else { + // XXX this could be more efficient + NS_ConvertASCIItoUTF16 unicodeString(utf8Chars, aLength); + nsCAutoString utf8; + PRInt32 headerLen = AppendDirectionalIndicatorUTF8(isRTL, utf8); + AppendUTF16toUTF8(unicodeString, utf8); + Init(aParams, utf8.get(), utf8.Length(), headerLen, nsnull, 0); + } +} - PangoGlyphUnit offset = 0; - int num_invalid_glyphs = 0; - for (gint i = 0; i < aGlyphs->num_glyphs; ++i) { - PangoGlyphInfo* info = &aGlyphs->glyphs[i]; - // Skip glyph when it is PANGO_GLYPH_EMPTY (0x0FFFFFFF) or has - // PANGO_GLYPH_UNKNOWN_FLAG (0x10000000) bit set. - if ((info->glyph & 0x10000000) || info->glyph == 0x0FFFFFFF) { - // XXX we should to render a slug for the invalid glyph instead of just skipping it - num_invalid_glyphs++; - } else { - glyphs[i-num_invalid_glyphs].index = info->glyph; - glyphs[i-num_invalid_glyphs].x = pt.x + (offset + info->geometry.x_offset)/FLOAT_PANGO_SCALE; - glyphs[i-num_invalid_glyphs].y = pt.y + (info->geometry.y_offset)/FLOAT_PANGO_SCALE; +gfxPangoTextRun::gfxPangoTextRun(gfxPangoFontGroup *aGroup, + const PRUnichar* aString, PRUint32 aLength, + gfxTextRunFactory::Parameters* aParams) + : gfxTextRun(aParams, PR_FALSE), mFontGroup(aGroup), mCharacterCount(aLength) +{ + mCharacterGlyphs = new CompressedGlyph[aLength]; + if (!mCharacterGlyphs) + return; + + if (mFlags & gfxTextRunFactory::TEXT_HAS_SURROGATES) { + // Record surrogates as parts of clusters now. This helps us + // convert UTF8/UTF16 coordinates + PRUint32 i; + for (i = 0; i < aLength; ++i) { + if (NS_IS_LOW_SURROGATE(aString[i])) { + mCharacterGlyphs[i].SetComplex(CompressedGlyph::TAG_LOW_SURROGATE); + } } - - offset += info->geometry.width; } - cairo_show_glyphs(ctx->GetCairo(), glyphs, aGlyphs->num_glyphs-num_invalid_glyphs); - - if (aGlyphs->num_glyphs > AUTO_GLYPHBUF_SIZE) - delete [] glyphs; - - cairo_font_face_destroy(font); - - return offset; -} -#endif - -struct TextSegment { - nsRefPtr mFont; - PRUint32 mOffset; - PRUint32 mLength; - int mWidth; - PangoGlyphString *mGlyphString; -}; - -gfxPangoTextRun::gfxPangoTextRun(const nsAString& aString, gfxPangoFontGroup *aGroup) - : mString(aString), mGroup(aGroup), mWidth(-1) -{ - mUTF8String = NS_ConvertUTF16toUTF8(aString); + nsCAutoString utf8; + PRInt32 headerLen = AppendDirectionalIndicatorUTF8(IsRightToLeft(), utf8); + AppendUTF16toUTF8(Substring(aString, aString + aLength), utf8); + Init(aParams, utf8.get(), utf8.Length(), headerLen, aString, aLength); } gfxPangoTextRun::~gfxPangoTextRun() { - for (PRUint32 i = 0; i < mSegments.Length(); ++i) { - if (mSegments[i].mGlyphString) - pango_glyph_string_free(mSegments[i].mGlyphString); - } - mSegments.Clear(); } void -gfxPangoTextRun::Draw(gfxContext *aContext, gfxPoint pt) +gfxPangoTextRun::Init(gfxTextRunFactory::Parameters* aParams, const gchar* aUTF8Text, + PRUint32 aUTF8Length, PRUint32 aUTF8HeaderLength, + const PRUnichar* aUTF16Text, PRUint32 aUTF16Length) { - MeasureOrDraw(aContext, PR_TRUE, pt); + SetupPangoContextDirection(); + nsresult rv = CreateGlyphRunsFast(aUTF8Text + aUTF8HeaderLength, + aUTF8Length - aUTF8HeaderLength, aUTF16Text, aUTF16Length); + if (rv == NS_ERROR_FAILURE) { + CreateGlyphRunsItemizing(aUTF8Text, aUTF8Length, aUTF8HeaderLength); + } +} + +void +gfxPangoTextRun::SetupPangoContextDirection() +{ + pango_context_set_base_dir(mFontGroup->GetFontAt(0)->GetPangoContext(), + (mFlags & gfxTextRunFactory::TEXT_IS_RTL) + ? PANGO_DIRECTION_RTL : PANGO_DIRECTION_LTR); +} + +static PRUint8 +GetFlagsForCharacter(gfxPangoTextRun::CompressedGlyph* aData) +{ + return (aData->IsClusterStart() ? gfxTextRun::CLUSTER_START : 0) + | (aData->IsLigatureContinuation() ? gfxTextRun::CONTINUES_LIGATURE : 0) + | (aData->CanBreakBefore() ? gfxTextRun::LINE_BREAK_BEFORE : 0); +} + +void +gfxPangoTextRun::GetCharFlags(PRUint32 aStart, PRUint32 aLength, + PRUint8* aFlags) +{ + NS_ASSERTION(aStart + aLength <= mCharacterCount, "Substring out of range"); + + if (!mCharacterGlyphs) { + memset(aFlags, CLUSTER_START, aLength); + return; + } + PRUint32 i; + for (i = 0; i < aLength; ++i) { + aFlags[i] = GetFlagsForCharacter(&mCharacterGlyphs[aStart + i]); + } +} + +PRUint8 +gfxPangoTextRun::GetCharFlags(PRUint32 aOffset) +{ + NS_ASSERTION(aOffset < mCharacterCount, "Character out of range"); + + if (!mCharacterGlyphs) + return 0; + return GetFlagsForCharacter(&mCharacterGlyphs[aOffset]); +} + +PRUint32 +gfxPangoTextRun::GetLength() +{ + return mCharacterCount; +} + +PRBool +gfxPangoTextRun::SetPotentialLineBreaks(PRUint32 aStart, PRUint32 aLength, + PRPackedBool* aBreakBefore) +{ + NS_ASSERTION(aStart + aLength <= mCharacterCount, "Overflow"); + + if (!mCharacterGlyphs) + return PR_TRUE; + PRUint32 changed = 0; + PRUint32 i; + for (i = 0; i < aLength; ++i) { + NS_ASSERTION(!aBreakBefore[i] || + mCharacterGlyphs[aStart + i].IsClusterStart(), + "Break suggested inside cluster!"); + changed |= mCharacterGlyphs[aStart + i].SetCanBreakBefore(aBreakBefore[i]); + } + return changed != 0; } gfxFloat -gfxPangoTextRun::Measure(gfxContext *aContext) +gfxPangoTextRun::ComputeClusterAdvance(PRUint32 aClusterOffset) { - if (mWidth == -1) { - static const gfxPoint kZeroZero(0, 0); - mWidth = MeasureOrDraw(aContext, PR_FALSE, kZeroZero); + CompressedGlyph* glyphData = &mCharacterGlyphs[aClusterOffset]; + if (glyphData->IsSimpleGlyph()) + return glyphData->GetSimpleAdvance(); + NS_ASSERTION(glyphData->IsComplexCluster(), "Unknown character type!"); + NS_ASSERTION(mDetailedGlyphs, "Complex cluster but no details array!"); + gfxFloat advance = 0; + DetailedGlyph* details = mDetailedGlyphs[aClusterOffset]; + NS_ASSERTION(details, "Complex cluster but no details!"); + for (;;) { + advance += details->mAdvance; + if (details->mIsLastGlyph) + return advance; + ++details; } - - return mWidth/FLOAT_PANGO_SCALE; } -int -gfxPangoTextRun::MeasureOrDraw(gfxContext *aContext, - PRBool aDraw, - gfxPoint aPt) +gfxPangoTextRun::LigatureData +gfxPangoTextRun::ComputeLigatureData(PRUint32 aPartOffset, PropertyProvider* aProvider) { - gfxMatrix mat; - nsRefPtr pf = mGroup->GetFontAt(0); - PRBool isRTL = IsRightToLeft(); - pango_context_set_base_dir(pf->GetPangoContext(), - isRTL ? PANGO_DIRECTION_RTL : - PANGO_DIRECTION_LTR); + LigatureData result; - if (aDraw) { - mat = aContext->CurrentMatrix(); - - // note that to make this work with pango_cairo, changing - // this Translate to a MoveTo makes things render in the right place. - // -I don't know why-. I mean, I assume it means that the path - // then starts in the right place, but that makes no sense, - // since a translate should do the exact same thing. Since - // pango-cairo is not going to be the default case, we'll - // just leave this in here for now and puzzle over it later. -#ifndef THEBES_USE_PANGO_CAIRO - aContext->Translate(aPt); -#else - pango_cairo_update_context(aContext->GetCairo(), pf->GetPangoContext()); -#endif + PRUint32 ligStart = aPartOffset; + CompressedGlyph* charGlyphs = mCharacterGlyphs; + while (charGlyphs[ligStart].IsLigatureContinuation()) { + do { + NS_ASSERTION(ligStart > 0, "Ligature at the start of the run??"); + --ligStart; + } while (!charGlyphs[ligStart].IsClusterStart()); } + result.mStartOffset = ligStart; + result.mLigatureWidth = ComputeClusterAdvance(ligStart)*mPixelsToAppUnits; + result.mPartClusterIndex = PR_UINT32_MAX; - int result; - - if (mSegments.IsEmpty()) - result = MeasureOrDrawItemizing(aContext, aDraw, aPt, isRTL, pf); - else - result = DrawFromCache(aContext, aPt); - - if (aDraw) - aContext->SetMatrix(mat); - - return result; -} - -int -gfxPangoTextRun::DrawFromCache(gfxContext *aContext, gfxPoint aPt) -{ - int result = 0; - int drawingOffset = 0; - for (PRUint32 i = 0; i < mSegments.Length(); i++) { - PangoGlyphString *glyphString = mSegments[i].mGlyphString; - if (glyphString->num_glyphs > 0) { -#ifndef THEBES_USE_PANGO_CAIRO - DrawCairoGlyphs(aContext, mSegments[i].mFont->GetPangoFont(), - gfxPoint(drawingOffset/FLOAT_PANGO_SCALE, 0.0), - glyphString); -#else - aContext->MoveTo(aPt); // XXX Do we need? - pango_cairo_show_glyph_string(aContext->GetCairo(), - mSegments[i].mFont->GetPangoFont(), - glyphString); -#endif - drawingOffset += mSegments[i].mWidth; + PRUint32 charIndex = ligStart; + // Count the number of started clusters we have seen + PRUint32 clusterCount = 0; + while (charIndex < mCharacterCount) { + if (charIndex == aPartOffset) { + result.mPartClusterIndex = clusterCount; } - result += mSegments[i].mWidth; + if (mCharacterGlyphs[charIndex].IsClusterStart()) { + if (charIndex > ligStart && + !mCharacterGlyphs[charIndex].IsLigatureContinuation()) + break; + ++clusterCount; + } + ++charIndex; + } + result.mClusterCount = clusterCount; + result.mEndOffset = charIndex; + + if (aProvider && (mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING)) { + PropertyProvider::Spacing spacing; + aProvider->GetSpacing(ligStart, 1, &spacing); + result.mBeforeSpacing = spacing.mBefore; + aProvider->GetSpacing(charIndex - 1, 1, &spacing); + result.mAfterSpacing = spacing.mAfter; + } else { + result.mBeforeSpacing = result.mAfterSpacing = 0; + } + + NS_ASSERTION(result.mPartClusterIndex < PR_UINT32_MAX, "Didn't find cluster part???"); + return result; +} + +void +gfxPangoTextRun::GetAdjustedSpacing(PRUint32 aStart, PRUint32 aEnd, + PropertyProvider* aProvider, + PropertyProvider::Spacing* aSpacing) +{ + if (aStart >= aEnd) + return; + + aProvider->GetSpacing(aStart, aEnd, aSpacing); + + // XXX the following loop could be avoided if we add some kind of + // TEXT_HAS_LIGATURES flag + CompressedGlyph* charGlyphs = mCharacterGlyphs; + PRUint32 i; + PRUint32 end = PR_MIN(aEnd, mCharacterCount - 1); + for (i = aStart; i <= end; ++i) { + if (charGlyphs[i].IsLigatureContinuation()) { + if (i < aEnd) { + aSpacing[i - aStart].mBefore = 0; + } + if (i > aStart) { + aSpacing[i - 1 - aStart].mAfter = 0; + } + } + } + + if (mFlags & gfxTextRunFactory::TEXT_ABSOLUTE_SPACING) { + // Subtract character widths from mAfter at the end of clusters/ligatures to + // relativize spacing. This is a bit sad since we're going to add + // them in again below when we actually use the spacing, but this + // produces simpler code and absolute spacing is rarely required. + + // The width of the last nonligature cluster, in appunits + gfxFloat clusterWidth = 0.0; + for (i = aStart; i < aEnd; ++i) { + CompressedGlyph* glyphData = &charGlyphs[i]; + + if (glyphData->IsSimpleGlyph()) { + if (i > aStart) { + aSpacing[i - 1 - aStart].mAfter -= clusterWidth; + } + clusterWidth = glyphData->GetSimpleAdvance()*mPixelsToAppUnits; + } else if (glyphData->IsComplexCluster()) { + NS_ASSERTION(mDetailedGlyphs, "No details but we have a complex cluster..."); + if (i > aStart) { + aSpacing[i - 1 - aStart].mAfter -= clusterWidth; + } + DetailedGlyph* details = mDetailedGlyphs[i]; + clusterWidth = 0.0; + for (;;) { + clusterWidth += details->mAdvance; + if (details->mIsLastGlyph) + break; + ++details; + } + clusterWidth *= mPixelsToAppUnits; + } + } + aSpacing[aEnd - 1 - aStart].mAfter -= clusterWidth; + } +} + +PRBool +gfxPangoTextRun::GetAdjustedSpacingArray(PRUint32 aStart, PRUint32 aEnd, + PropertyProvider* aProvider, + nsTArray* aSpacing) +{ + if (!(mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING)) + return PR_FALSE; + if (!aSpacing->AppendElements(aEnd - aStart)) + return PR_FALSE; + GetAdjustedSpacing(aStart, aEnd, aProvider, aSpacing->Elements()); + return PR_TRUE; +} + +void +gfxPangoTextRun::ProcessCairoGlyphsWithSpacing(CairoGlyphProcessorCallback aCB, void* aClosure, + gfxPoint* aPt, PRUint32 aStart, PRUint32 aEnd, + PropertyProvider::Spacing* aSpacing) +{ + if (aStart >= aEnd) + return; + + double appUnitsToPixels = 1/mPixelsToAppUnits; + CompressedGlyph* charGlyphs = mCharacterGlyphs; + double direction = GetDirection(); + + nsAutoTArray glyphBuffer; + PRUint32 i; + double x = aPt->x; + double y = aPt->y; + + PRBool isRTL = IsRightToLeft(); + + if (aSpacing) { + x += direction*aSpacing[0].mBefore*appUnitsToPixels; + } + for (i = aStart; i < aEnd; ++i) { + CompressedGlyph* glyphData = &charGlyphs[i]; + if (glyphData->IsSimpleGlyph()) { + cairo_glyph_t* glyph = glyphBuffer.AppendElement(); + if (!glyph) + return; + glyph->index = glyphData->GetSimpleGlyph(); + double advance = glyphData->GetSimpleAdvance(); + glyph->x = x; + glyph->y = y; + if (isRTL) { + glyph->x -= advance; + x -= advance; + } else { + x += advance; + } + } else if (glyphData->IsComplexCluster()) { + NS_ASSERTION(mDetailedGlyphs, "No details but we have a complex cluster..."); + DetailedGlyph* details = mDetailedGlyphs[i]; + for (;;) { + // Don't try to rendering missing glyphs, this causes weird problems + double advance = details->mAdvance; + if (details->mGlyphID != DetailedGlyph::DETAILED_MISSING_GLYPH) { + cairo_glyph_t* glyph = glyphBuffer.AppendElement(); + if (!glyph) + return; + glyph->index = details->mGlyphID; + glyph->x = x + details->mXOffset; + glyph->y = y + details->mYOffset; + if (isRTL) { + glyph->x -= advance; + } + } + x += direction*advance; + if (details->mIsLastGlyph) + break; + ++details; + } + } + if (aSpacing) { + double space = aSpacing[i - aStart].mAfter; + if (i + 1 < aEnd) { + space += aSpacing[i + 1 - aStart].mBefore; + } + x += direction*space*appUnitsToPixels; + } + } + if (aSpacing) { + x += direction*aSpacing[aEnd - 1 - aStart].mAfter*appUnitsToPixels; + } + + *aPt = gfxPoint(x, y); + + aCB(aClosure, glyphBuffer.Elements(), glyphBuffer.Length()); +} + +void +gfxPangoTextRun::ShrinkToLigatureBoundaries(PRUint32* aStart, PRUint32* aEnd) +{ + if (*aStart >= *aEnd) + return; + + CompressedGlyph* charGlyphs = mCharacterGlyphs; + + NS_ASSERTION(charGlyphs[*aStart].IsClusterStart(), + "Started in the middle of a cluster..."); + NS_ASSERTION(*aEnd == mCharacterCount || charGlyphs[*aEnd].IsClusterStart(), + "Ended in the middle of a cluster..."); + + if (charGlyphs[*aStart].IsLigatureContinuation()) { + LigatureData data = ComputeLigatureData(*aStart, nsnull); + *aStart = data.mEndOffset; + } + if (*aEnd < mCharacterCount && charGlyphs[*aEnd].IsLigatureContinuation()) { + LigatureData data = ComputeLigatureData(*aEnd, nsnull); + *aEnd = data.mStartOffset; + } +} + +void +gfxPangoTextRun::CairoShowGlyphs(void* aClosure, cairo_glyph_t* aGlyphs, int aNumGlyphs) +{ + cairo_show_glyphs(NS_STATIC_CAST(cairo_t*, aClosure), aGlyphs, aNumGlyphs); +} + +void +gfxPangoTextRun::CairoGlyphsToPath(void* aClosure, cairo_glyph_t* aGlyphs, int aNumGlyphs) +{ + cairo_glyph_path(NS_STATIC_CAST(cairo_t*, aClosure), aGlyphs, aNumGlyphs); +} + +void +gfxPangoTextRun::ProcessCairoGlyphs(CairoGlyphProcessorCallback aCB, void* aClosure, + gfxPoint* aPt, PRUint32 aStart, PRUint32 aEnd, + PropertyProvider* aProvider) +{ + if (!(mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING)) { + ProcessCairoGlyphsWithSpacing(aCB, aClosure, aPt, aStart, aEnd, nsnull); + return; + } + + nsAutoTArray spacingBuffer; + if (!spacingBuffer.AppendElements(aEnd - aStart)) + return; + GetAdjustedSpacing(aStart, aEnd, aProvider, spacingBuffer.Elements()); + ProcessCairoGlyphsWithSpacing(aCB, aClosure, aPt, aStart, aEnd, spacingBuffer.Elements()); +} + +gfxFloat +gfxPangoTextRun::GetPartialLigatureWidth(PRUint32 aStart, PRUint32 aEnd, + PropertyProvider* aProvider) +{ + if (aStart >= aEnd) + return 0; + + LigatureData data = ComputeLigatureData(aStart, aProvider); + PRUint32 clusterCount = 0; + PRUint32 i; + for (i = aStart; i < aEnd; ++i) { + if (mCharacterGlyphs[i].IsClusterStart()) { + ++clusterCount; + } + } + + gfxFloat result = data.mLigatureWidth*clusterCount/data.mClusterCount; + if (aStart == data.mStartOffset) { + result += data.mBeforeSpacing; + } + if (aEnd == data.mEndOffset) { + result += data.mAfterSpacing; } return result; } +void +gfxPangoTextRun::DrawPartialLigature(gfxContext* aCtx, PRUint32 aOffset, + const gfxRect* aDirtyRect, gfxPoint* aPt, + PropertyProvider* aProvider) +{ + NS_ASSERTION(aDirtyRect, "Cannot draw partial ligatures without a dirty rect"); + + if (!mCharacterGlyphs[aOffset].IsClusterStart() || !aDirtyRect) + return; + + gfxFloat appUnitsToPixels = 1.0/mPixelsToAppUnits; + + // Draw partial ligature. We hack this by clipping the ligature. + LigatureData data = ComputeLigatureData(aOffset, aProvider); + // Width of a cluster in the ligature, in device pixels + gfxFloat clusterWidth = data.mLigatureWidth*appUnitsToPixels/data.mClusterCount; + + gfxFloat direction = GetDirection(); + gfxFloat left = aDirtyRect->X()*appUnitsToPixels; + gfxFloat right = aDirtyRect->XMost()*appUnitsToPixels; + // The advance to the start of this cluster in the drawn ligature, in device pixels + gfxFloat widthBeforeCluster; + // Any spacing that should be included after the cluster, in device pixels + gfxFloat afterSpace; + if (data.mStartOffset < aOffset) { + // Not the start of the ligature; need to clip the ligature before the current cluster + if (IsRightToLeft()) { + right = PR_MIN(right, aPt->x); + } else { + left = PR_MAX(left, aPt->x); + } + widthBeforeCluster = clusterWidth*data.mPartClusterIndex + + data.mBeforeSpacing/mPixelsToAppUnits; + } else { + // We're drawing the start of the ligature, so our cluster includes any + // before-spacing. + widthBeforeCluster = 0; + } + if (aOffset < data.mEndOffset) { + // Not the end of the ligature; need to clip the ligature after the current cluster + gfxFloat endEdge = aPt->x + clusterWidth; + if (IsRightToLeft()) { + left = PR_MAX(left, endEdge); + } else { + right = PR_MIN(right, endEdge); + } + afterSpace = 0; + } else { + afterSpace = data.mAfterSpacing/mPixelsToAppUnits; + } + + aCtx->Save(); + aCtx->Clip(gfxRect(left, aDirtyRect->Y()*appUnitsToPixels, right - left, + aDirtyRect->Height()*appUnitsToPixels)); + gfxPoint pt(aPt->x - direction*widthBeforeCluster, aPt->y); + ProcessCairoGlyphs(CairoShowGlyphs, aCtx->GetCairo(), &pt, data.mStartOffset, + data.mEndOffset, aProvider); + aCtx->Restore(); + + aPt->x += direction*(clusterWidth + afterSpace); +} + +void +gfxPangoTextRun::Draw(gfxContext* aContext, gfxPoint aPt, + PRUint32 aStart, PRUint32 aLength, const gfxRect* aDirtyRect, + PropertyProvider* aProvider, gfxFloat* aAdvanceWidth) +{ + NS_ASSERTION(aStart + aLength <= mCharacterCount, "Substring out of range"); + + gfxFloat appUnitsToPixels = 1/mPixelsToAppUnits; + CompressedGlyph* charGlyphs = mCharacterGlyphs; + gfxFloat direction = GetDirection(); + + gfxPoint pt(NSToCoordRound(aPt.x*appUnitsToPixels), + NSToCoordRound(aPt.y*appUnitsToPixels)); + gfxFloat startX = pt.x; + + GlyphRunIterator iter(this, aStart, aLength); + while (iter.NextRun()) { + cairo_t* cr = aContext->GetCairo(); + SetupCairoFont(cr, iter.GetGlyphRun()); + + PRUint32 start = iter.GetStringStart(); + PRUint32 end = iter.GetStringEnd(); + NS_ASSERTION(charGlyphs[start].IsClusterStart(), + "Started drawing in the middle of a cluster..."); + NS_ASSERTION(end == mCharacterCount || charGlyphs[end].IsClusterStart(), + "Ended drawing in the middle of a cluster..."); + + PRUint32 ligatureRunStart = start; + PRUint32 ligatureRunEnd = end; + ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd); + + PRUint32 i; + for (i = start; i < ligatureRunStart; ++i) { + DrawPartialLigature(aContext, i, aDirtyRect, &pt, aProvider); + } + + ProcessCairoGlyphs(CairoShowGlyphs, cr, &pt, ligatureRunStart, + ligatureRunEnd, aProvider); + + for (i = ligatureRunEnd; i < end; ++i) { + DrawPartialLigature(aContext, i, aDirtyRect, &pt, aProvider); + } + } + + if (aAdvanceWidth) { + *aAdvanceWidth = (pt.x - startX)*direction*mPixelsToAppUnits; + } +} + +void +gfxPangoTextRun::DrawToPath(gfxContext* aContext, gfxPoint aPt, + PRUint32 aStart, PRUint32 aLength, + PropertyProvider* aProvider, gfxFloat* aAdvanceWidth) +{ + NS_ASSERTION(aStart + aLength <= mCharacterCount, "Substring out of range"); + + gfxFloat appUnitsToPixels = 1/mPixelsToAppUnits; + CompressedGlyph* charGlyphs = mCharacterGlyphs; + gfxFloat direction = GetDirection(); + + gfxPoint pt(NSToCoordRound(aPt.x*appUnitsToPixels), + NSToCoordRound(aPt.y*appUnitsToPixels)); + gfxFloat startX = pt.x; + + GlyphRunIterator iter(this, aStart, aLength); + while (iter.NextRun()) { + cairo_t* cr = aContext->GetCairo(); + SetupCairoFont(cr, iter.GetGlyphRun()); + + PRUint32 start = iter.GetStringStart(); + PRUint32 end = iter.GetStringEnd(); + NS_ASSERTION(charGlyphs[start].IsClusterStart(), + "Started drawing path in the middle of a cluster..."); + NS_ASSERTION(!charGlyphs[start].IsLigatureContinuation(), + "Can't draw path starting inside ligature"); + NS_ASSERTION(end == mCharacterCount || charGlyphs[end].IsClusterStart(), + "Ended drawing path in the middle of a cluster..."); + NS_ASSERTION(end == mCharacterCount || !charGlyphs[end].IsLigatureContinuation(), + "Can't end drawing path inside ligature"); + + ProcessCairoGlyphs(CairoGlyphsToPath, cr, &pt, start, end, aProvider); + } + + if (aAdvanceWidth) { + *aAdvanceWidth = (pt.x - startX)*direction*mPixelsToAppUnits; + } +} + +static gfxTextRun::Metrics +GetPangoMetrics(PangoGlyphString* aGlyphs, PangoFont* aPangoFont, + gfxFloat aPixelsToUnits, PRUint32 aClusterCount) +{ + PangoRectangle inkRect; + PangoRectangle logicalRect; + pango_glyph_string_extents(aGlyphs, aPangoFont, &inkRect, &logicalRect); + + gfxFloat scale = aPixelsToUnits/PANGO_SCALE; + + gfxTextRun::Metrics metrics; + NS_ASSERTION(logicalRect.x == 0, "Weird logical rect..."); + metrics.mAdvanceWidth = logicalRect.width*scale; + metrics.mAscent = PANGO_ASCENT(logicalRect)*scale; + metrics.mDescent = PANGO_DESCENT(logicalRect)*scale; + gfxFloat x = inkRect.x*scale; + gfxFloat y = inkRect.y*scale; + metrics.mBoundingBox = gfxRect(x, y, (inkRect.x + inkRect.width)*scale - x, + (inkRect.y + inkRect.height)*scale - y); + metrics.mClusterCount = aClusterCount; + return metrics; +} + +void +gfxPangoTextRun::AccumulatePangoMetricsForRun(PangoFont* aPangoFont, PRUint32 aStart, + PRUint32 aEnd, PropertyProvider* aProvider, Metrics* aMetrics) +{ + if (aEnd <= aStart) + return; + + CompressedGlyph* charGlyphs = mCharacterGlyphs; + + nsAutoTArray glyphBuffer; + + nsAutoTArray spacingBuffer; + PRBool haveSpacing = GetAdjustedSpacingArray(aStart, aEnd, aProvider, &spacingBuffer); + gfxFloat appUnitsToPango = gfxFloat(PANGO_SCALE)/mPixelsToAppUnits; + + // We start by assuming every character is a cluster and subtract off + // characters where that's not true + PRUint32 clusterCount = aEnd - aStart; + + PRUint32 i; + for (i = aStart; i < aEnd; ++i) { + PRUint32 leftSpacePango = 0; + PRUint32 rightSpacePango = 0; + if (haveSpacing) { + PropertyProvider::Spacing* space = &spacingBuffer[i]; + leftSpacePango = + NSToIntRound((IsRightToLeft() ? space->mAfter : space->mBefore)*appUnitsToPango); + rightSpacePango = + NSToIntRound((IsRightToLeft() ? space->mBefore : space->mAfter)*appUnitsToPango); + } + CompressedGlyph* glyphData = &charGlyphs[i]; + if (glyphData->IsSimpleGlyph()) { + PangoGlyphInfo* glyphInfo = glyphBuffer.AppendElement(); + if (!glyphInfo) + return; + glyphInfo->attr.is_cluster_start = 0; + glyphInfo->glyph = glyphData->GetSimpleGlyph(); + glyphInfo->geometry.width = leftSpacePango + rightSpacePango + + glyphData->GetSimpleAdvance()*PANGO_SCALE; + glyphInfo->geometry.x_offset = leftSpacePango; + glyphInfo->geometry.y_offset = 0; + } else if (glyphData->IsComplexCluster()) { + NS_ASSERTION(mDetailedGlyphs, "No details but we have a complex cluster..."); + DetailedGlyph* details = mDetailedGlyphs[i]; + PRBool firstGlyph = PR_FALSE; + for (;;) { + PangoGlyphInfo* glyphInfo = glyphBuffer.AppendElement(); + if (!glyphInfo) + return; + glyphInfo->attr.is_cluster_start = 0; + glyphInfo->glyph = details->mGlyphID; + glyphInfo->geometry.width = NSToIntRound(details->mAdvance*PANGO_SCALE); + glyphInfo->geometry.x_offset = NSToIntRound(details->mXOffset*PANGO_SCALE); + if (firstGlyph) { + glyphInfo->geometry.width += leftSpacePango; + glyphInfo->geometry.x_offset += leftSpacePango; + firstGlyph = PR_FALSE; + } + glyphInfo->geometry.y_offset = NSToIntRound(details->mYOffset*PANGO_SCALE); + if (details->mIsLastGlyph) { + glyphInfo->geometry.width += rightSpacePango; + break; + } + ++details; + } + } else if (!glyphData->IsClusterStart()) { + --clusterCount; + } + } + + if (IsRightToLeft()) { + // Reverse glyph order + for (i = 0; i < (glyphBuffer.Length() >> 1); ++i) { + PangoGlyphInfo* a = &glyphBuffer[i]; + PangoGlyphInfo* b = &glyphBuffer[glyphBuffer.Length() - 1 - i]; + PangoGlyphInfo tmp = *a; + *a = *b; + *b = tmp; + } + } + + PangoGlyphString glyphs; + glyphs.num_glyphs = glyphBuffer.Length(); + glyphs.glyphs = glyphBuffer.Elements(); + glyphs.log_clusters = nsnull; + glyphs.space = glyphBuffer.Length(); + Metrics metrics = GetPangoMetrics(&glyphs, aPangoFont, mPixelsToAppUnits, clusterCount); + + if (IsRightToLeft()) { + metrics.CombineWith(*aMetrics); + *aMetrics = metrics; + } else { + aMetrics->CombineWith(metrics); + } +} + +void +gfxPangoTextRun::AccumulatePartialLigatureMetrics(PangoFont* aPangoFont, + PRUint32 aOffset, PropertyProvider* aProvider, Metrics* aMetrics) +{ + if (!mCharacterGlyphs[aOffset].IsClusterStart()) + return; + + // Measure partial ligature. We hack this by clipping the metrics in the + // same way we clip the drawing. + LigatureData data = ComputeLigatureData(aOffset, aProvider); + + // First measure the complete ligature + Metrics metrics; + AccumulatePangoMetricsForRun(aPangoFont, data.mStartOffset, data.mEndOffset, + aProvider, &metrics); + gfxFloat clusterWidth = data.mLigatureWidth/data.mClusterCount; + + gfxFloat bboxStart; + if (IsRightToLeft()) { + bboxStart = metrics.mAdvanceWidth - metrics.mBoundingBox.XMost(); + } else { + bboxStart = metrics.mBoundingBox.X(); + } + gfxFloat bboxEnd = bboxStart + metrics.mBoundingBox.size.width; + + gfxFloat widthBeforeCluster; + gfxFloat totalWidth = clusterWidth; + if (data.mStartOffset < aOffset) { + widthBeforeCluster = + clusterWidth*data.mPartClusterIndex + data.mBeforeSpacing; + // Not the start of the ligature; need to clip the boundingBox start + bboxStart = PR_MAX(bboxStart, widthBeforeCluster); + } else { + // We're at the start of the ligature, so our cluster includes any + // before-spacing and no clipping is required on this edge + widthBeforeCluster = 0; + totalWidth += data.mBeforeSpacing; + } + if (aOffset < data.mEndOffset) { + // Not the end of the ligature; need to clip the boundingBox end + gfxFloat endEdge = widthBeforeCluster + clusterWidth; + bboxEnd = PR_MIN(bboxEnd, endEdge); + } else { + totalWidth += data.mAfterSpacing; + } + bboxStart -= widthBeforeCluster; + bboxEnd -= widthBeforeCluster; + if (IsRightToLeft()) { + metrics.mBoundingBox.pos.x = metrics.mAdvanceWidth - bboxEnd; + } else { + metrics.mBoundingBox.pos.x = bboxStart; + } + metrics.mBoundingBox.size.width = bboxEnd - bboxStart; + + // We want metrics for just one cluster of the ligature + metrics.mAdvanceWidth = totalWidth; + metrics.mClusterCount = 1; + + if (IsRightToLeft()) { + metrics.CombineWith(*aMetrics); + *aMetrics = metrics; + } else { + aMetrics->CombineWith(metrics); + } +} + +gfxTextRun::Metrics +gfxPangoTextRun::MeasureText(PRUint32 aStart, PRUint32 aLength, + PRBool aTightBoundingBox, + PropertyProvider* aProvider) +{ + CompressedGlyph* charGlyphs = mCharacterGlyphs; + + NS_ASSERTION(aStart + aLength <= mCharacterCount, "Substring out of range"); + NS_ASSERTION(aStart == mCharacterCount || charGlyphs[aStart].IsClusterStart(), + "MeasureText called, not starting at cluster boundary"); + NS_ASSERTION(aStart + aLength == mCharacterCount || + charGlyphs[aStart + aLength].IsClusterStart(), + "MeasureText called, not ending at cluster boundary"); + + Metrics accumulatedMetrics; + GlyphRunIterator iter(this, aStart, aLength); + while (iter.NextRun()) { + PangoFont* font = iter.GetGlyphRun()->mPangoFont; + PRUint32 ligatureRunStart = iter.GetStringStart(); + PRUint32 ligatureRunEnd = iter.GetStringEnd(); + ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd); + + PRUint32 i; + for (i = iter.GetStringStart(); i < ligatureRunStart; ++i) { + AccumulatePartialLigatureMetrics(font, i, aProvider, &accumulatedMetrics); + } + + // XXX This sucks. We have to make up the Pango glyphstring and call + // pango_glyph_string_extents just so we can detect glyphs outside the font + // box, even when aTightBoundingBox is false, even though in almost all + // cases we could get correct results just by getting some ascent/descent + // from the font and using our stored advance widths. + AccumulatePangoMetricsForRun(font, + ligatureRunStart, ligatureRunEnd, aProvider, &accumulatedMetrics); + + for (i = ligatureRunEnd; i < iter.GetStringEnd(); ++i) { + AccumulatePartialLigatureMetrics(font, i, aProvider, &accumulatedMetrics); + } + } + + return accumulatedMetrics; +} + +#define MEASUREMENT_BUFFER_SIZE 100 + +PRUint32 +gfxPangoTextRun::BreakAndMeasureText(PRUint32 aStart, PRUint32 aMaxLength, + PRBool aLineBreakBefore, gfxFloat aWidth, + PropertyProvider* aProvider, + PRBool aSuppressInitialBreak, + Metrics* aMetrics, PRBool aTightBoundingBox, + PRBool* aUsedHyphenation, + PRUint32* aLastBreak) +{ + CompressedGlyph* charGlyphs = mCharacterGlyphs; + + aMaxLength = PR_MIN(aMaxLength, mCharacterCount - aStart); + + NS_ASSERTION(aStart + aMaxLength <= mCharacterCount, "Substring out of range"); + NS_ASSERTION(aStart == mCharacterCount || charGlyphs[aStart].IsClusterStart(), + "BreakAndMeasureText called, not starting at cluster boundary"); + NS_ASSERTION(aStart + aMaxLength == mCharacterCount || + charGlyphs[aStart + aMaxLength].IsClusterStart(), + "BreakAndMeasureText called, not ending at cluster boundary"); + + PRUint32 bufferStart = aStart; + PRUint32 bufferLength = PR_MIN(aMaxLength, MEASUREMENT_BUFFER_SIZE); + PropertyProvider::Spacing spacingBuffer[MEASUREMENT_BUFFER_SIZE]; + PRBool haveSpacing = (mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING) != 0; + if (haveSpacing) { + GetAdjustedSpacing(bufferStart, bufferStart + bufferLength, aProvider, + spacingBuffer); + } + PRPackedBool hyphenBuffer[MEASUREMENT_BUFFER_SIZE]; + PRBool haveHyphenation = (mFlags & gfxTextRunFactory::TEXT_ENABLE_HYPHEN_BREAKS) != 0; + if (haveHyphenation) { + aProvider->GetHyphenationBreaks(bufferStart, bufferStart + bufferLength, + hyphenBuffer); + } + + gfxFloat width = 0; + PRUint32 pixelAdvance = 0; + gfxFloat floatAdvanceUnits = 0; + PRInt32 lastBreak = -1; + PRBool aborted = PR_FALSE; + PRUint32 end = aStart + aMaxLength; + PRBool lastBreakUsedHyphenation = PR_FALSE; + + PRUint32 ligatureRunStart = aStart; + PRUint32 ligatureRunEnd = end; + ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd); + + PRUint32 i; + for (i = aStart; i < end; ++i) { + if (i >= bufferStart + bufferLength) { + // Fetch more spacing and hyphenation data + bufferStart = i; + bufferLength = PR_MIN(aStart + aMaxLength, i + MEASUREMENT_BUFFER_SIZE) - i; + if (haveSpacing) { + GetAdjustedSpacing(bufferStart, bufferStart + bufferLength, aProvider, + spacingBuffer); + } + if (haveHyphenation) { + aProvider->GetHyphenationBreaks(bufferStart, bufferStart + bufferLength, + hyphenBuffer); + } + } + + PRBool lineBreakHere = mCharacterGlyphs[i].CanBreakBefore() && + (!aSuppressInitialBreak || i > aStart); + if (lineBreakHere || (haveHyphenation && hyphenBuffer[i - bufferStart])) { + gfxFloat advance = gfxFloat(pixelAdvance)*mPixelsToAppUnits + floatAdvanceUnits; + gfxFloat hyphenatedAdvance = advance; + PRBool hyphenation = !lineBreakHere; + if (hyphenation) { + hyphenatedAdvance += aProvider->GetHyphenWidth(); + } + pixelAdvance = 0; + floatAdvanceUnits = 0; + + if (lastBreak < 0 || width + hyphenatedAdvance <= aWidth) { + // We can break here. + lastBreak = i; + lastBreakUsedHyphenation = hyphenation; + } + + width += advance; + if (width > aWidth) { + // No more text fits. Abort + aborted = PR_TRUE; + break; + } + } + + if (i >= ligatureRunStart && i < ligatureRunEnd) { + CompressedGlyph* glyphData = &charGlyphs[i]; + if (glyphData->IsSimpleGlyph()) { + pixelAdvance += glyphData->GetSimpleAdvance(); + } else if (glyphData->IsComplexCluster()) { + NS_ASSERTION(mDetailedGlyphs, "No details but we have a complex cluster..."); + DetailedGlyph* details = mDetailedGlyphs[i]; + for (;;) { + floatAdvanceUnits += details->mAdvance*mPixelsToAppUnits; + if (details->mIsLastGlyph) + break; + ++details; + } + } + if (haveSpacing) { + PropertyProvider::Spacing* space = &spacingBuffer[i - bufferStart]; + floatAdvanceUnits += space->mBefore + space->mAfter; + } + } else { + floatAdvanceUnits += GetPartialLigatureWidth(i, i + 1, aProvider); + } + } + + if (!aborted) { + gfxFloat advance = gfxFloat(pixelAdvance)*mPixelsToAppUnits + floatAdvanceUnits; + width += advance; + } + + // There are three possibilities: + // 1) all the text fit (width <= aWidth) + // 2) some of the text fit up to a break opportunity (width > aWidth && lastBreak >= 0) + // 3) none of the text fits before a break opportunity (width > aWidth && lastBreak < 0) + PRUint32 charsFit; + if (width <= aWidth) { + charsFit = aMaxLength; + } else if (lastBreak >= 0) { + charsFit = lastBreak - aStart; + } else { + charsFit = aMaxLength; + } + + if (aMetrics) { + *aMetrics = MeasureText(aStart, charsFit, aTightBoundingBox, aProvider); + } + if (aUsedHyphenation) { + *aUsedHyphenation = lastBreakUsedHyphenation; + } + if (aLastBreak && charsFit == aMaxLength) { + if (lastBreak < 0) { + *aLastBreak = PR_UINT32_MAX; + } else { + *aLastBreak = lastBreak - aStart; + } + } + + return charsFit; +} + +gfxFloat +gfxPangoTextRun::GetAdvanceWidth(PRUint32 aStart, PRUint32 aLength, + PropertyProvider* aProvider) +{ + CompressedGlyph* charGlyphs = mCharacterGlyphs; + + NS_ASSERTION(aStart + aLength <= mCharacterCount, "Substring out of range"); + NS_ASSERTION(aStart == mCharacterCount || charGlyphs[aStart].IsClusterStart(), + "GetAdvanceWidth called, not starting at cluster boundary"); + NS_ASSERTION(aStart + aLength == mCharacterCount || + charGlyphs[aStart + aLength].IsClusterStart(), + "GetAdvanceWidth called, not ending at cluster boundary"); + + gfxFloat result = 0; // app units + + // Account for all spacing here. This is more efficient than processing it + // along with the glyphs. + if (mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING) { + PRUint32 i; + nsAutoTArray spacingBuffer; + if (spacingBuffer.AppendElements(aLength)) { + GetAdjustedSpacing(aStart, aStart + aLength, aProvider, + spacingBuffer.Elements()); + for (i = 0; i < aLength; ++i) { + PropertyProvider::Spacing* space = &spacingBuffer[i]; + result += space->mBefore + space->mAfter; + } + } + } + + PRUint32 pixelAdvance = 0; + PRUint32 ligatureRunStart = aStart; + PRUint32 ligatureRunEnd = aStart + aLength; + ShrinkToLigatureBoundaries(&ligatureRunStart, &ligatureRunEnd); + + result += GetPartialLigatureWidth(aStart, ligatureRunStart, aProvider) + + GetPartialLigatureWidth(ligatureRunEnd, aStart + aLength, aProvider); + + PRUint32 i; + for (i = ligatureRunStart; i < ligatureRunEnd; ++i) { + CompressedGlyph* glyphData = &charGlyphs[i]; + if (glyphData->IsSimpleGlyph()) { + pixelAdvance += glyphData->GetSimpleAdvance(); + } else if (glyphData->IsComplexCluster()) { + NS_ASSERTION(mDetailedGlyphs, "No details but we have a complex cluster..."); + DetailedGlyph* details = mDetailedGlyphs[i]; + for (;;) { + result += details->mAdvance*mPixelsToAppUnits; + if (details->mIsLastGlyph) + break; + ++details; + } + } + } + + return result + gfxFloat(pixelAdvance)*mPixelsToAppUnits; +} + #define IS_MISSING_GLYPH(g) (((g) & 0x10000000) || (g) == 0x0FFFFFFF) +static PangoGlyphString* +GetPangoGlyphsFor(gfxPangoFont* aFont, const char* aText) +{ + PangoAnalysis analysis; + analysis.font = aFont->GetPangoFont(); + analysis.level = 0; + analysis.lang_engine = nsnull; + analysis.extra_attrs = nsnull; + analysis.language = pango_language_from_string("en"); + analysis.shape_engine = pango_font_find_shaper(analysis.font, + analysis.language, + 'a'); + + PangoGlyphString* glyphString = pango_glyph_string_new(); + pango_shape(aText, strlen(aText), &analysis, glyphString); + + PRUint32 i; + for (i = 0; i < PRUint32(glyphString->num_glyphs); ++i) { + if (IS_MISSING_GLYPH(glyphString->glyphs[i].glyph)) { + pango_glyph_string_free(glyphString); + return nsnull; + } + } + + return glyphString; +} + +static const gfxPangoFontGroup::SpecialStringData* +GetSpecialStringData(gfxTextRun::SpecialString aString, gfxPangoFontGroup* aFontGroup) +{ + NS_ASSERTION(aString <= gfxTextRun::STRING_MAX, "Unknown special string"); + gfxPangoFontGroup::SpecialStringData* data = &aFontGroup->mSpecialStrings[aString]; + if (!data->mGlyphs) { + static PRUint8 utf8Hyphen[] = { 0xE2, 0x80, 0x90, 0 }; // U+2010 + static PRUint8 utf8Ellipsis[] = { 0xE2, 0x80, 0xA6, 0 }; // U+2026 + + const char* text; + switch (aString) { + case gfxTextRun::STRING_ELLIPSIS: + text = NS_REINTERPRET_CAST(const char*, utf8Ellipsis); + break; + case gfxTextRun::STRING_HYPHEN: + text = NS_REINTERPRET_CAST(const char*, utf8Hyphen); + break; + default: + case gfxTextRun::STRING_SPACE: text = " "; break; + } + + gfxPangoFont* font = aFontGroup->GetFontAt(0); + data->mGlyphs = GetPangoGlyphsFor(font, text); + if (!data->mGlyphs) { + switch (aString) { + case gfxTextRun::STRING_ELLIPSIS: text = "..."; break; + case gfxTextRun::STRING_HYPHEN: text = "-"; break; + default: + NS_WARNING("This font doesn't support the space character? That's messed up"); + return nsnull; + } + data->mGlyphs = GetPangoGlyphsFor(font, text); + if (!data->mGlyphs) { + NS_WARNING("This font doesn't support hyphen-minus or full-stop characters? That's messed up"); + return nsnull; + } + } + + PangoRectangle logicalRect; + pango_glyph_string_extents(data->mGlyphs, font->GetPangoFont(), nsnull, &logicalRect); + data->mAdvance = gfxFloat(logicalRect.width)/PANGO_SCALE; + } + return data; +} + +static cairo_scaled_font_t* +CreateScaledFont(cairo_t* aCR, cairo_matrix_t* aCTM, PangoFont* aPangoFont) +{ + // XXX is this safe really? We should probably check the font type or something. + // XXX does this really create the same font that Pango used for measurement? + // We probably need to work harder here. We should pay particular attention + // to the font options. + PangoFcFont* fcfont = PANGO_FC_FONT(aPangoFont); + cairo_font_face_t* face = cairo_ft_font_face_create_for_pattern(fcfont->font_pattern); + double size; + if (FcPatternGetDouble(fcfont->font_pattern, FC_PIXEL_SIZE, 0, &size) != FcResultMatch) + size = 12.0; + cairo_matrix_t fontMatrix; + cairo_matrix_init_scale(&fontMatrix, size, size); + cairo_font_options_t* fontOptions = cairo_font_options_create(); + cairo_get_font_options(aCR, fontOptions); + cairo_scaled_font_t* scaledFont = + cairo_scaled_font_create(face, &fontMatrix, aCTM, fontOptions); + cairo_font_options_destroy(fontOptions); + return scaledFont; +} + +gfxTextRun::Metrics +gfxPangoTextRun::MeasureTextSpecialString(SpecialString aString, + PRBool aTightBoundingBox) +{ + const gfxPangoFontGroup::SpecialStringData* data = GetSpecialStringData(aString, mFontGroup); + if (!data) + return Metrics(); + + PangoFont* pangoFont = mFontGroup->GetFontAt(0)->GetPangoFont(); + return GetPangoMetrics(data->mGlyphs, pangoFont, mPixelsToAppUnits, 1); +} + +void +gfxPangoTextRun::DrawSpecialString(gfxContext *aContext, gfxPoint aPt, + SpecialString aString) +{ + gfxFloat appUnitsToPixels = 1/mPixelsToAppUnits; + gfxPoint pt(NSToCoordRound(aPt.x*appUnitsToPixels), + NSToCoordRound(aPt.y*appUnitsToPixels)); + const gfxPangoFontGroup::SpecialStringData* data = GetSpecialStringData(aString, mFontGroup); + if (!data) + return; + + if (IsRightToLeft()) { + pt.x -= data->mAdvance; + } + aContext->MoveTo(pt); + + cairo_glyph_t glyphs[3]; + PRUint32 glyphCount = data->mGlyphs->num_glyphs; + if (glyphCount > NS_ARRAY_LENGTH(glyphs)) { + NS_WARNING("Special string requires more than 3 glyphs ... that is bogus"); + return; + } + + PRUint32 i; + double x = 0; + for (i = 0; i < glyphCount; ++i) { + PangoGlyphInfo* pangoGlyph = &data->mGlyphs->glyphs[i]; + cairo_glyph_t glyph = + { pangoGlyph->glyph, + pangoGlyph->geometry.x_offset + x, pangoGlyph->geometry.y_offset }; + x += pangoGlyph->geometry.width; + // The glyphs in special-strings are always drawn left to right + glyphs[i] = glyph; + } + + cairo_t* cr = aContext->GetCairo(); + PangoFont* pangoFont = mFontGroup->GetFontAt(0)->GetPangoFont(); + if (mGlyphRuns[0].mPangoFont == pangoFont) { + // Use cached font if possible + SetupCairoFont(cr, &mGlyphRuns[0]); + } else { + cairo_matrix_t matrix; + cairo_get_matrix(cr, &matrix); + cairo_scaled_font_t* font = CreateScaledFont(cr, &matrix, pangoFont); + cairo_set_scaled_font(cr, font); + cairo_scaled_font_destroy(font); + } + + cairo_show_glyphs(cr, glyphs, glyphCount); +} + +gfxFloat +gfxPangoTextRun::GetAdvanceWidthSpecialString(SpecialString aString) +{ + return GetSpecialStringData(aString, mFontGroup)->mAdvance*mPixelsToAppUnits; +} + +gfxFont::Metrics +gfxPangoTextRun::GetDecorationMetrics() +{ + gfxFont::Metrics metrics = mFontGroup->GetFontAt(0)->GetMetrics(); + metrics.xHeight *= mPixelsToAppUnits; + metrics.superscriptOffset *= mPixelsToAppUnits; + metrics.subscriptOffset *= mPixelsToAppUnits; + metrics.strikeoutSize *= mPixelsToAppUnits; + metrics.strikeoutOffset *= mPixelsToAppUnits; + metrics.underlineSize *= mPixelsToAppUnits; + metrics.underlineOffset *= mPixelsToAppUnits; + metrics.height *= mPixelsToAppUnits; + metrics.internalLeading *= mPixelsToAppUnits; + metrics.externalLeading *= mPixelsToAppUnits; + metrics.emHeight *= mPixelsToAppUnits; + metrics.emAscent *= mPixelsToAppUnits; + metrics.emDescent *= mPixelsToAppUnits; + metrics.maxHeight *= mPixelsToAppUnits; + metrics.maxAscent *= mPixelsToAppUnits; + metrics.maxDescent *= mPixelsToAppUnits; + metrics.maxAdvance *= mPixelsToAppUnits; + metrics.aveCharWidth *= mPixelsToAppUnits; + metrics.spaceWidth *= mPixelsToAppUnits; + return metrics; +} + +void +gfxPangoTextRun::FlushSpacingCache(PRUint32 aStart) +{ + // Do nothing for now because we don't cache spacing +} + +void +gfxPangoTextRun::SetLineBreaks(PRUint32 aStart, PRUint32 aLength, + PRBool aLineBreakBefore, PRBool aLineBreakAfter, + TextProvider* aProvider, + gfxFloat* aAdvanceWidthDelta) +{ + // Do nothing because our shaping does not currently take linebreaks into + // account. There is no change in advance width. + if (aAdvanceWidthDelta) { + *aAdvanceWidthDelta = 0; + } +} + +void +gfxPangoTextRun::SetupCairoFont(cairo_t* aCR, GlyphRun* aGlyphRun) +{ + cairo_matrix_t currentCTM; + cairo_get_matrix(aCR, ¤tCTM); + + if (aGlyphRun->mCairoFont) { + // Need to validate that its CTM is OK + cairo_matrix_t fontCTM; + cairo_scaled_font_get_ctm(aGlyphRun->mCairoFont, &fontCTM); + if (fontCTM.xx == currentCTM.xx && fontCTM.yy == currentCTM.yy && + fontCTM.xy == currentCTM.xy && fontCTM.yx == currentCTM.yx) { + cairo_set_scaled_font(aCR, aGlyphRun->mCairoFont); + return; + } + + // Just recreate it from scratch, simplest way + cairo_scaled_font_destroy(aGlyphRun->mCairoFont); + } + + aGlyphRun->mCairoFont = CreateScaledFont(aCR, ¤tCTM, aGlyphRun->mPangoFont); + cairo_set_scaled_font(aCR, aGlyphRun->mCairoFont); +} + +PRUint32 +gfxPangoTextRun::FindFirstGlyphRunContaining(PRUint32 aOffset) +{ + NS_ASSERTION(aOffset <= mCharacterCount, "Bad offset looking for glyphrun"); + if (aOffset == mCharacterCount) + return mGlyphRuns.Length(); + + PRUint32 start = 0; + PRUint32 end = mGlyphRuns.Length(); + while (end - start > 1) { + PRUint32 mid = (start + end)/2; + if (mGlyphRuns[mid].mCharacterOffset <= aOffset) { + start = mid; + } else { + end = mid; + } + } + NS_ASSERTION(mGlyphRuns[start].mCharacterOffset <= aOffset, + "Hmm, something went wrong, aOffset should have been found"); + return start; +} + +void +gfxPangoTextRun::SetupClusterBoundaries(const gchar* aUTF8, PRUint32 aUTF8Length, + PRUint32 aUTF16Offset, PangoAnalysis* aAnalysis) +{ + if (mFlags & gfxTextRunFactory::TEXT_IS_8BIT) { + // 8-bit text doesn't have clusters. + // XXX is this true in all languages??? + return; + } + + nsAutoTArray buffer; + if (!buffer.AppendElements(aUTF8Length + 1)) + return; + + // Pango says "the array of PangoLogAttr passed in must have at least N+1 + // elements, if there are N characters in the text being broken" + pango_break(aUTF8, aUTF8Length, aAnalysis, buffer.Elements(), aUTF8Length + 1); + + PRUint32 index = 0; + while (index < aUTF8Length) { + if (!buffer[index].is_cursor_position) { + mCharacterGlyphs[aUTF16Offset].SetClusterContinuation(); + } + ++aUTF16Offset; + if (aUTF16Offset < mCharacterCount && + mCharacterGlyphs[aUTF16Offset].IsLowSurrogate()) { + ++aUTF16Offset; + } + // We produced this utf8 so we don't need to worry about malformed stuff + index = g_utf8_next_char(aUTF8 + index) - aUTF8; + } +} + +nsresult +gfxPangoTextRun::AddGlyphRun(PangoFont* aFont, PRUint32 aUTF16Offset) +{ + GlyphRun* glyphRun = mGlyphRuns.AppendElement(); + if (!glyphRun) + return NS_ERROR_OUT_OF_MEMORY; + + g_object_ref(aFont); + glyphRun->mPangoFont = aFont; + glyphRun->mCairoFont = nsnull; + glyphRun->mCharacterOffset = aUTF16Offset; + return NS_OK; +} + +nsresult +gfxPangoTextRun::SetGlyphs(const gchar* aUTF8, PRUint32 aUTF8Length, + PRUint32* aUTF16Offset, PangoGlyphString* aGlyphs, + PangoGlyphUnit aOverrideSpaceWidth, + PRBool aAbortOnMissingGlyph) +{ + PRUint32 utf16Offset = *aUTF16Offset; + PRUint32 index = 0; + // glyphIndex is the first glyph that belongs to characters at "index" + // or later + PRUint32 numGlyphs = aGlyphs->num_glyphs; + gint* logClusters = aGlyphs->log_clusters; + PRUint32 glyphCount = 0; + PRUint32 glyphIndex = IsRightToLeft() ? numGlyphs - 1 : 0; + PRInt32 direction = IsRightToLeft() ? -1 : 1; + + while (index < aUTF8Length) { + // Clear existing glyph details + if (mDetailedGlyphs) { + mDetailedGlyphs[utf16Offset] = nsnull; + } + + if (aUTF8[index] == 0) { + // treat this null byte like a missing glyph with no advance + if (!mDetailedGlyphs) { + mDetailedGlyphs = new nsAutoArrayPtr[mCharacterCount]; + if (!mDetailedGlyphs) + return NS_ERROR_OUT_OF_MEMORY; + } + DetailedGlyph* details = new DetailedGlyph[1]; + if (!details) + return NS_ERROR_OUT_OF_MEMORY; + mDetailedGlyphs[utf16Offset] = details; + details->mIsLastGlyph = PR_TRUE; + details->mGlyphID = DetailedGlyph::DETAILED_MISSING_GLYPH; + details->mAdvance = 0; + details->mXOffset = 0; + details->mYOffset = 0; + } else if (glyphCount == numGlyphs || + PRUint32(logClusters[glyphIndex]) > index) { + // No glyphs for this cluster, and it's not a null byte. It must be a ligature. + mCharacterGlyphs[utf16Offset].SetLigatureContinuation(); + } else { + PangoGlyphInfo* glyph = &aGlyphs->glyphs[glyphIndex]; + if (aAbortOnMissingGlyph && IS_MISSING_GLYPH(glyph->glyph)) + return NS_ERROR_FAILURE; + + // One or more glyphs. See if we fit in the compressed area. + NS_ASSERTION(PRUint32(logClusters[glyphIndex]) == index, + "Um, we left some glyphs behind previously"); + PRUint32 glyphClusterCount = 1; + for (;;) { + ++glyphCount; + if (aAbortOnMissingGlyph && IS_MISSING_GLYPH(aGlyphs->glyphs[glyphIndex].glyph)) + return NS_ERROR_FAILURE; + glyphIndex += direction; + if (glyphCount == numGlyphs || + PRUint32(logClusters[glyphIndex]) != index) + break; + ++glyphClusterCount; + } + + PangoGlyphUnit width = glyph->geometry.width; + + // Override the width of a space, but only for spaces that aren't + // clustered with something else (like a freestanding diacritical mark) + if (aOverrideSpaceWidth && aUTF8[index] == ' ' && + (utf16Offset + 1 == mCharacterCount || + mCharacterGlyphs[utf16Offset].IsClusterStart())) { + width = aOverrideSpaceWidth; + } + + PRInt32 downscaledWidth = width/PANGO_SCALE; + if (glyphClusterCount == 1 && + glyph->geometry.x_offset == 0 && glyph->geometry.y_offset == 0 && + width >= 0 && downscaledWidth*PANGO_SCALE == width && + CompressedGlyph::IsSimpleAdvance(downscaledWidth) && + CompressedGlyph::IsSimpleGlyphID(glyph->glyph)) { + mCharacterGlyphs[utf16Offset].SetSimpleGlyph(downscaledWidth, glyph->glyph); + } else { + // Note that missing-glyph IDs are not simple glyph IDs, so we'll + // always get here when a glyph is missing + if (!mDetailedGlyphs) { + mDetailedGlyphs = new nsAutoArrayPtr[mCharacterCount]; + if (!mDetailedGlyphs) + return NS_ERROR_OUT_OF_MEMORY; + } + DetailedGlyph* details = new DetailedGlyph[glyphClusterCount]; + if (!details) + return NS_ERROR_OUT_OF_MEMORY; + mDetailedGlyphs[utf16Offset] = details; + PRUint32 i; + for (i = 0; i < glyphClusterCount; ++i) { + details->mIsLastGlyph = i == glyphClusterCount - 1; + details->mGlyphID = glyph->glyph; + NS_ASSERTION(details->mGlyphID == glyph->glyph, + "Seriously weird glyph ID detected!"); + if (IS_MISSING_GLYPH(glyph->glyph)) { + details->mGlyphID = DetailedGlyph::DETAILED_MISSING_GLYPH; + } + details->mAdvance = float(glyph->geometry.width)/PANGO_SCALE; + details->mXOffset = float(glyph->geometry.x_offset)/PANGO_SCALE; + details->mYOffset = float(glyph->geometry.y_offset)/PANGO_SCALE; + glyph += direction; + ++details; + } + } + } + + ++utf16Offset; + if (utf16Offset < mCharacterCount && + mCharacterGlyphs[utf16Offset].IsLowSurrogate()) { + ++utf16Offset; + } + // We produced this UTF8 so we don't need to worry about malformed stuff + index = g_utf8_next_char(aUTF8 + index) - aUTF8; + } + + *aUTF16Offset = utf16Offset; + return NS_OK; +} + +nsresult +gfxPangoTextRun::CreateGlyphRunsFast(const gchar* aUTF8, PRUint32 aUTF8Length, + const PRUnichar* aUTF16, PRUint32 aUTF16Length) +{ + gfxPangoFont* font = mFontGroup->GetFontAt(0); + PangoAnalysis analysis; + analysis.font = font->GetPangoFont(); + analysis.level = IsRightToLeft() ? 1 : 0; + analysis.lang_engine = nsnull; + analysis.extra_attrs = nsnull; + + // If we're passed an empty string for some reason, ensure that at least + // a single glyphrun object is created, because DrawSpecialString assumes + // there is one. (So does FindFirstGlyphRunContaining, but that shouldn't + // be called on an empty string.) + if (!aUTF8Length) + return AddGlyphRun(analysis.font, 0); + + // Find non-ASCII character for finding the language of the script. + guint32 ch = 'a'; + PRUint8 unicharRange = kRangeSetLatin; + if (aUTF16) { + PRUint32 i; + for (i = 0; i < aUTF16Length; ++i) { + PRUnichar utf16Char = aUTF16[i]; + if (utf16Char > 0x100) { + ch = utf16Char; + unicharRange = FindCharUnicodeRange(utf16Char); + break; + } + } + } + + // Determin the language for finding the shaper. + nsCAutoString lang; + font->GetMozLang(lang); + switch (unicharRange) { + case kRangeSetLatin: + lang.Assign("x-western"); + break; + case kRangeSetCJK: + if (GetCJKLangGroupIndex(lang.get()) < 0) + return NS_ERROR_FAILURE; // try with itemizing + break; + default: + lang.Assign(LangGroupFromUnicodeRange(unicharRange)); + break; + } + + if (lang.IsEmpty() || lang.Equals("x-unicode") || lang.Equals("x-user-def")) + return NS_ERROR_FAILURE; // try with itemizing + + analysis.language = GetPangoLanguage(lang); + analysis.shape_engine = pango_font_find_shaper(analysis.font, + analysis.language, + ch); + + SetupClusterBoundaries(aUTF8, aUTF8Length, 0, &analysis); + + PangoGlyphString* glyphString = pango_glyph_string_new(); + + pango_shape(aUTF8, aUTF8Length, &analysis, glyphString); + + PRUint32 utf16Offset = 0; + nsresult rv = SetGlyphs(aUTF8, aUTF8Length, &utf16Offset, glyphString, 0, PR_TRUE); + + pango_glyph_string_free(glyphString); + + if (NS_FAILED(rv)) + return rv; + + return AddGlyphRun(analysis.font, 0); +} + class FontSelector { public: - FontSelector(gfxContext *aContext, const char* aString, PRInt32 aLength, - gfxPangoFontGroup *aGroup, PangoItem *aItem, - PRPackedBool aIsRTL) : - mContext(aContext), mItem(aItem), - mGroup(aGroup), mFontIndex(0), - mString(aString), mLength(aLength), - mSegmentOffset(0), mSegmentIndex(0), + FontSelector(const gchar* aString, PRInt32 aLength, + gfxPangoFontGroup *aGroup, gfxPangoTextRun* aTextRun, + PangoItem *aItem, PRUint32 aUTF16Offset, PRPackedBool aIsRTL) : + mItem(aItem), + mGroup(aGroup), mTextRun(aTextRun), mString(aString), + mFontIndex(0), mLength(aLength), mSegmentOffset(0), mUTF16Offset(aUTF16Offset), mTriedPrefFonts(0), mTriedOtherFonts(0), mIsRTL(aIsRTL) { for (PRUint32 i = 0; i < mGroup->FontListLength(); ++i) mFonts.AppendElement(mGroup->GetFontAt(i)); - mShouldCheckByShaping = ShapingNeededForInit(); + mSpaceWidth = NSToIntRound(mGroup->GetFontAt(0)->GetMetrics().spaceWidth * FLOAT_PANGO_SCALE); + } + + void Run() + { InitSegments(0, mLength, mFontIndex); } - ~FontSelector() { - mSegmentStack.Clear(); - mFonts.Clear(); - } + + PRUint32 GetUTF16Offset() { return mUTF16Offset; } static PRBool ExistsFont(FontSelector *aFs, const nsAString &aName) { @@ -1015,200 +2473,147 @@ public: return PR_TRUE; } - PRBool GetNextSegment(TextSegment *aTextSegment) { - if (mSegmentIndex >= mSegmentStack.Length()) - return PR_FALSE; + PRBool AppendNextSegment(gfxPangoFont* aFont, PRUint32 aUTF8Length, + PangoGlyphString* aGlyphs, PRBool aGotGlyphs) + { + PangoFont* pf = aFont->GetPangoFont(); + PRUint32 incomingUTF16Offset = mUTF16Offset; - SegmentData segment = mSegmentStack[mSegmentIndex++]; + if (!aGotGlyphs) { + // we can't use the existing glyphstring. + PangoFont *tmpFont = mItem->analysis.font; + mItem->analysis.font = pf; + pango_shape(mString + mSegmentOffset, aUTF8Length, &mItem->analysis, aGlyphs); + mItem->analysis.font = tmpFont; + } - aTextSegment->mFont = segment.mFont; - aTextSegment->mOffset = mSegmentOffset; - aTextSegment->mLength = segment.mLength; - aTextSegment->mWidth = 0; - aTextSegment->mGlyphString = segment.mGlyphString; - mSegmentOffset += segment.mLength; - return PR_TRUE; + mTextRun->SetGlyphs(mString + mSegmentOffset, aUTF8Length, &mUTF16Offset, + aGlyphs, mSpaceWidth, PR_FALSE); + + mSegmentOffset += aUTF8Length; + return mTextRun->AddGlyphRun(pf, incomingUTF16Offset); } + private: - nsRefPtr mContext; PangoItem *mItem; nsTArray< nsRefPtr > mFonts; gfxPangoFontGroup *mGroup; + gfxPangoTextRun *mTextRun; + const char *mString; // UTF-8 PRUint32 mFontIndex; - - const char *mString; // UTF-8 - PRInt32 mLength; - - struct SegmentData { - PRUint32 mLength; - nsRefPtr mFont; - PangoGlyphString *mGlyphString; - }; - PRUint32 mSegmentOffset; - PRUint32 mSegmentIndex; - nsTArray mSegmentStack; + PRInt32 mLength; + PRUint32 mSegmentOffset; + PRUint32 mUTF16Offset; + PRUint32 mSpaceWidth; PRPackedBool mTriedPrefFonts; PRPackedBool mTriedOtherFonts; PRPackedBool mIsRTL; - PRPackedBool mShouldCheckByShaping; void InitSegments(const PRUint32 aOffset, const PRUint32 aLength, const PRUint32 aFontIndex) { - if (aLength == 0) - return; mFontIndex = aFontIndex; - const char *start = mString + aOffset; + + const char *current = mString + aOffset; + PRBool checkMissingGlyph = PR_TRUE; + + // for RTL, if we cannot find the font that has all glyphs, + // we should use better font. + PRUint32 betterFontIndex = 0; + PRUint32 foundGlyphs = 0; + + PangoGlyphString *glyphString = pango_glyph_string_new(); RetryNextFont: nsRefPtr font = GetNextFont(); + + // If we cannot found the font that has the current character glyph, + // we should return default font's missing data. if (!font) { - AppendMissingSegment(aOffset, aLength); - return; - } - PangoFcFont *fcFont = PANGO_FC_FONT(font->GetPangoFont()); - - const char *last = start + aLength; - for (const char *c = start; c < last;) { - const char *checking = c; - gunichar u = UTF8CharEnumerator::NextChar(&c, last, nsnull); - if (pango_fc_font_has_char(fcFont, u)) - continue; - - // create the segment for found glyphs - if (!AppendSegment(start - mString, checking - start, font)) - goto RetryNextFont; - - const char *c2 = c; - for (; c2 < last;) { - u = UTF8CharEnumerator::NextChar(&c2, last, nsnull); - if (!pango_fc_font_has_char(fcFont, u)) - continue; - } - - // the current font doesn't have all characters. - if (checking == start && c2 == last) - goto RetryNextFont; - - // missing glyphs - PRUint32 fontIndex = mFontIndex; - InitSegments(checking - mString, c2 - checking, mFontIndex); - mFontIndex = fontIndex; - - start = c = c2; + font = mFonts[betterFontIndex]; + checkMissingGlyph = PR_FALSE; } - if (!AppendSegment(start - mString, last - start, font)) - goto RetryNextFont; - } - - PRBool AppendSegment(const PRUint32 aOffset, - const PRUint32 aLength, - const nsRefPtr aFont) { - if (aLength == 0) - return PR_TRUE; - - SegmentData segment; - segment.mLength = aLength; - segment.mFont = aFont; - - segment.mGlyphString = pango_glyph_string_new(); + // Shaping PangoFont *tmpFont = mItem->analysis.font; - mItem->analysis.font = aFont->GetPangoFont(); - pango_shape(mString + aOffset, aLength, &mItem->analysis, - segment.mGlyphString); + mItem->analysis.font = font->GetPangoFont(); + pango_shape(current, aLength, &mItem->analysis, glyphString); mItem->analysis.font = tmpFont; - if (!mShouldCheckByShaping) { - mSegmentStack.AppendElement(segment); - return PR_TRUE; - } - for (PRInt32 i = 0; i < segment.mGlyphString->num_glyphs; i++) { - if (IS_MISSING_GLYPH(segment.mGlyphString->glyphs[i].glyph)) { - NS_WARNING("The glyph is existing, but failed to shape"); - pango_glyph_string_free(segment.mGlyphString); - return PR_FALSE; + gint num_glyphs = glyphString->num_glyphs; + gint *clusters = glyphString->log_clusters; + PRUint32 offset = aOffset; + PRUint32 skipLength = 0; + if (checkMissingGlyph) { + for (PRInt32 i = 0; i < num_glyphs; ++i) { + PangoGlyphInfo* info = &glyphString->glyphs[i]; + if (IS_MISSING_GLYPH(info->glyph)) { + // XXX Note that we don't support the segment separation + // in RTL text. Because the Arabic characters changes the + // glyphs by the position of the context. I think that the + // languages of RTL doesn't have *very* many characters, so, + // the Arabic/Hebrew font may have all glyphs in a font. + if (mIsRTL) { + PRUint32 found = i; + for (PRInt32 j = i; j < num_glyphs; ++j) { + info = &glyphString->glyphs[j]; + if (!IS_MISSING_GLYPH(info->glyph)) + found++; + } + if (found > foundGlyphs) { + // we find better font! + foundGlyphs = found; + betterFontIndex = mFontIndex - 1; + } + goto RetryNextFont; + } + + // The glyph is missing, separate segment here. + PRUint32 missingLength = aLength - clusters[i]; + PRInt32 j; + for (j = i + 1; j < num_glyphs; ++j) { + info = &glyphString->glyphs[j]; + if (!IS_MISSING_GLYPH(info->glyph)) { + missingLength = aOffset + clusters[j] - offset; + break; + } + } + + if (i != 0) { + // found glyphs + AppendNextSegment(font, offset - (aOffset + skipLength), + glyphString, PR_FALSE); + } + + // missing glyphs + PRUint32 fontIndex = mFontIndex; + InitSegments(offset, missingLength, mFontIndex); + mFontIndex = fontIndex; + + PRUint32 next = offset + missingLength; + if (next >= aLength) { + pango_glyph_string_free(glyphString); + return; + } + + // remains, continue this loop + i = j; + skipLength = next - aOffset; + } + if (i + 1 < num_glyphs) + offset = aOffset + clusters[i + 1]; + else + offset = aOffset + aLength; } + } else { + offset = aOffset + aLength; } - mSegmentStack.AppendElement(segment); - return PR_TRUE; - } - - void AppendMissingSegment(const PRUint32 aOffset, - const PRUint32 aLength) { - SegmentData segment; - segment.mLength = aLength; - segment.mGlyphString = GetMissingGlyphString(aOffset, aLength, 0); - segment.mFont = mFonts[0]; - mSegmentStack.AppendElement(segment); - } - -#define RANGE_NEEDS_SHAPING(u,min,max) if ((u) < (min)) continue; \ - if ((u) <= (max)) return PR_TRUE; - - PRBool ShapingNeededForInit() { - const char *c = mString; - const char *last = c + mLength; - for (; c < last;) { - PRUint32 u = UTF8CharEnumerator::NextChar(&c, last, nsnull); - // Arabic, Syriac, Arabic Supplement, N'Ko, Thaana - RANGE_NEEDS_SHAPING(u, 0x0600, 0x07FF) - // Devanagari, Bengali, Gurmukhi, Gujarati, Oriya, Tamil, Telugu, - // Kannada, Malayalam, Sinhala, Thai, Lao, Tibetan, Myanmar, - // Hangul Jamo - RANGE_NEEDS_SHAPING(u, 0x0900, 0x11FF) - // Gujarati - RANGE_NEEDS_SHAPING(u, 0x0A80, 0x0AFF) - // Khmer, Mongolian - RANGE_NEEDS_SHAPING(u, 0x1780, 0x18AF) - // Limbu, Tai Le, New Tai Lue, Khmer Symbols - RANGE_NEEDS_SHAPING(u, 0x1900, 0x19FF) - // Balinese - RANGE_NEEDS_SHAPING(u, 0x1B00, 0x1B7F) - // Syloti Nagri - RANGE_NEEDS_SHAPING(u, 0xA800, 0xA82F) - // Phags-pa - RANGE_NEEDS_SHAPING(u, 0xA840, 0xA87F) - // Alphabetic Presentation Forms, Arabic Presentaion Forms-A - RANGE_NEEDS_SHAPING(u, 0xFB00, 0xFDFF) - // Arabic Presentaion Forms-B - RANGE_NEEDS_SHAPING(u, 0xFE70, 0xFEFF) - // Kharoshthi - RANGE_NEEDS_SHAPING(u, 0x10A00, 0x10A5F) - } - return PR_FALSE; - } - - PangoGlyphString* GetMissingGlyphString(const PRUint32 aOffset, - const PRUint32 aLength, - const PRUint32 aFontIndex) { - PangoGlyphString *glyphString = pango_glyph_string_new(); - if (!glyphString) - return nsnull; - pango_glyph_string_set_size(glyphString, aLength); - const char *start = mString + aOffset; - const char *last = start + aLength; - gint32 spaceWidth = (gint32) - NSToCoordRound(mFonts[aFontIndex]->GetMetrics().spaceWidth * - FLOAT_PANGO_SCALE); - PRUint32 i = 0; - for (const char *c = start; c < last; i++) { - const char *checking = c; - UTF8CharEnumerator::NextChar(&c, last, nsnull); - PangoGlyphInfo *glyphs = &glyphString->glyphs[i]; - glyphs->glyph = 0x10000000; - glyphs->geometry.width = spaceWidth; - glyphs->geometry.x_offset = 0; - glyphs->geometry.y_offset = 0; - glyphs->attr.is_cluster_start = 1; - glyphString->log_clusters[i] = checking - start; - } - pango_glyph_string_set_size(glyphString, i); - return glyphString; + AppendNextSegment(font, aLength - skipLength, glyphString, skipLength == 0); + pango_glyph_string_free(glyphString); } gfxPangoFont *GetNextFont() { @@ -1317,113 +2722,39 @@ TRY_AGAIN_HOPE_FOR_THE_BEST_2: } }; -int -gfxPangoTextRun::MeasureOrDrawItemizing(gfxContext *aContext, - PRBool aDraw, - gfxPoint aPt, - PRBool aIsRTL, - gfxPangoFont *aFont) +void +gfxPangoTextRun::CreateGlyphRunsItemizing(const gchar* aUTF8, PRUint32 aUTF8Length, + PRUint32 aUTF8HeaderLen) { - GList *items = pango_itemize(aFont->GetPangoContext(), mUTF8String.get(), 0, - mUTF8String.Length(), NULL, NULL); - if (items) { - GList *tmp = items; - items = pango_reorder_items(tmp); - g_list_free(tmp); - } - gint32 spaceWidth = (gint32) - NSToCoordRound(aFont->GetMetrics().spaceWidth * FLOAT_PANGO_SCALE); - int width = 0; - int spacingOffset = 0; - int drawingOffset = 0; - PRBool spacing = aDraw && !mUTF8Spacing.IsEmpty(); - PRBool isRTL = aIsRTL; + GList *items = pango_itemize(mFontGroup->GetFontAt(0)->GetPangoContext(), aUTF8, 0, + aUTF8Length, nsnull, nsnull); + + PRUint32 utf16Offset = 0; + PRBool isRTL = IsRightToLeft(); for (; items && items->data; items = items->next) { PangoItem *item = (PangoItem *)items->data; - PRBool itemIsRTL = item->analysis.level % 2; - if ((itemIsRTL && !isRTL) || (!itemIsRTL && isRTL)) { - isRTL = itemIsRTL; - pango_context_set_base_dir(aFont->GetPangoContext(), - isRTL ? PANGO_DIRECTION_RTL : - PANGO_DIRECTION_LTR); + NS_ASSERTION(isRTL == item->analysis.level % 2, "RTL assumption mismatch"); + + PRUint32 offset = item->offset; + PRUint32 length = item->length; + if (offset < aUTF8HeaderLen) { + if (offset + length <= aUTF8HeaderLen) + continue; + length -= aUTF8HeaderLen - offset; + offset = aUTF8HeaderLen; } - FontSelector fs(aContext, mUTF8String.get() + item->offset, - item->length, mGroup, item, isRTL); - TextSegment segment; - while (fs.GetNextSegment(&segment)) { - PangoGlyphString *glyphString = segment.mGlyphString; - int currentWidth = 0; - for (int i = 0; i < glyphString->num_glyphs; i++) { - // Adjust spacing and fix the space width - int j = glyphString->log_clusters[i] + - item->offset + segment.mOffset; - PangoGlyphGeometry *geometry = &glyphString->glyphs[i].geometry; - if (spacing) { - geometry->x_offset = spacingOffset; - spacingOffset += mUTF8Spacing[j] - geometry->width; - } - if (mUTF8String[j] == ' ') - geometry->width = spaceWidth; - currentWidth += geometry->width; - } - -#ifdef THEBES_USE_PANGO_CAIRO - // XXX Do we need? - glyphString->glyphs[0].geometry.x_offset += drawingOffset; -#endif - - // drawing - if (aDraw && glyphString->num_glyphs > 0) { -#ifndef THEBES_USE_PANGO_CAIRO - DrawCairoGlyphs(aContext, segment.mFont->GetPangoFont(), - gfxPoint(drawingOffset/FLOAT_PANGO_SCALE, 0.0), - glyphString); -#else - aContext->MoveTo(aPt); // XXX Do we need? - pango_cairo_show_glyph_string(aContext->GetCairo(), - segment.mFont->GetPangoFont(), - glyphString); -#endif - drawingOffset += currentWidth; - } - - width += currentWidth; - segment.mWidth = currentWidth; - mSegments.AppendElement(segment); - } - - pango_item_free(item); - } + + SetupClusterBoundaries(aUTF8 + offset, length, utf16Offset, &item->analysis); + FontSelector fs(aUTF8 + offset, length, mFontGroup, this, item, utf16Offset, isRTL); + fs.Run(); // appends GlyphRuns + utf16Offset = fs.GetUTF16Offset(); + } + NS_ASSERTION(utf16Offset == mCharacterCount, + "Didn't resolve all characters"); + if (items) g_list_free(items); - - return width; -} - -void -gfxPangoTextRun::SetSpacing(const nsTArray &spacingArray) -{ - mSpacing = spacingArray; - NS_ConvertUTF16toUTF8 str(mString); - mUTF8Spacing.Clear(); - const char *curChar = str.get(); - const char *prevChar = curChar; - for (unsigned int i = 0; i < mString.Length(); i++) { - for (; prevChar + 1 < curChar; prevChar++) - mUTF8Spacing.AppendElement(0); - mUTF8Spacing.AppendElement((PRInt32)NSToCoordRound(mSpacing[i] * FLOAT_PANGO_SCALE)); - if (NS_IS_HIGH_SURROGATE(mString[i])) - i++; - prevChar = curChar; - curChar = g_utf8_find_next_char(curChar, NULL); - } -} - -const nsTArray *const -gfxPangoTextRun::GetSpacing() const -{ - return &mSpacing; } /** @@ -1710,4 +3041,3 @@ GetMozLanguage(const PangoLanguage *aLang, nsACString &aMozLang) break; } } - diff --git a/gfx/thebes/src/gfxTextRunCache.cpp b/gfx/thebes/src/gfxTextRunCache.cpp index 467a5ac3c27..987ad786d6c 100644 --- a/gfx/thebes/src/gfxTextRunCache.cpp +++ b/gfx/thebes/src/gfxTextRunCache.cpp @@ -85,53 +85,126 @@ gfxTextRunCache::Shutdown() } } -gfxTextRun* -gfxTextRunCache::GetOrMakeTextRun (gfxFontGroup *aFontGroup, const nsAString& aString) +static PRUint32 ComputeFlags(PRBool aIsRTL, PRBool aEnableSpacing) { - if (gDisableCache) - return aFontGroup->MakeTextRun(aString); + PRUint32 flags = gfxTextRunFactory::TEXT_HAS_SURROGATES; + if (aIsRTL) { + flags |= gfxTextRunFactory::TEXT_IS_RTL; + } + if (aEnableSpacing) { + flags |= gfxTextRunFactory::TEXT_ENABLE_SPACING | + gfxTextRunFactory::TEXT_ABSOLUTE_SPACING | + gfxTextRunFactory::TEXT_ENABLE_NEGATIVE_SPACING; + } + return flags; +} - // Evict first, to make sure that the textrun we return is live. - EvictUTF16(); +gfxTextRun* +gfxTextRunCache::GetOrMakeTextRun (gfxContext* aContext, gfxFontGroup *aFontGroup, + const PRUnichar *aString, PRUint32 aLength, gfxFloat aDevToApp, + PRBool aIsRTL, PRBool aEnableSpacing, PRBool *aCallerOwns) +{ + gfxSkipChars skipChars; + // Choose pessimistic flags since we don't want to bother analyzing the string + gfxTextRunFactory::Parameters params = + { aContext, nsnull, nsnull, &skipChars, nsnull, 0, aDevToApp, + ComputeFlags(aIsRTL, aEnableSpacing) }; - gfxTextRun *tr; - TextRunEntry *entry; - FontGroupAndString key(aFontGroup, &aString); + gfxTextRun* tr = nsnull; + // Don't cache textruns that use spacing + if (!gDisableCache && !aEnableSpacing) { + // Evict first, to make sure that the textrun we return is live. + EvictUTF16(); + + TextRunEntry *entry; + nsDependentSubstring keyStr(aString, aString + aLength); + FontGroupAndString key(aFontGroup, &keyStr); - if (mHashTableUTF16.Get(key, &entry)) { - entry->Used(); - tr = entry->textRun.get(); + if (mHashTableUTF16.Get(key, &entry)) { + gfxTextRun *cachedTR = entry->textRun; + // Check that this matches what we wanted. If it doesn't, we leave + // this cache entry alone and return a fresh, caller-owned textrun + // below. + if (cachedTR->GetPixelsToAppUnits() == aDevToApp && + cachedTR->IsRightToLeft() == aIsRTL) { + entry->Used(); + tr = cachedTR; + tr->SetContext(aContext); + } + } else { + tr = aFontGroup->MakeTextRun(aString, aLength, ¶ms); + entry = new TextRunEntry(tr); + key.Realize(); + mHashTableUTF16.Put(key, entry); + } + } + + if (tr) { + *aCallerOwns = PR_FALSE; } else { - tr = aFontGroup->MakeTextRun(aString); - entry = new TextRunEntry(tr); - key.Realize(); - mHashTableUTF16.Put(key, entry); + // Textrun is not in the cache for some reason. + *aCallerOwns = PR_TRUE; + tr = aFontGroup->MakeTextRun(aString, aLength, ¶ms); + } + if (tr) { + // We don't want to have to reconstruct the string + tr->RememberText(aString, aLength); } return tr; } gfxTextRun* -gfxTextRunCache::GetOrMakeTextRun (gfxFontGroup *aFontGroup, const nsACString& aString) +gfxTextRunCache::GetOrMakeTextRun (gfxContext* aContext, gfxFontGroup *aFontGroup, + const char *aString, PRUint32 aLength, gfxFloat aDevToApp, + PRBool aIsRTL, PRBool aEnableSpacing, PRBool *aCallerOwns) { - if (gDisableCache) - return aFontGroup->MakeTextRun(aString); + gfxSkipChars skipChars; + // Choose pessimistic flags since we don't want to bother analyzing the string + gfxTextRunFactory::Parameters params = + { aContext, nsnull, nsnull, &skipChars, nsnull, 0, aDevToApp, + ComputeFlags(aIsRTL, aEnableSpacing) }; + const PRUint8* str = NS_REINTERPRET_CAST(const PRUint8*, aString); - // Evict first, to make sure that the textrun we return is live. - EvictASCII(); + gfxTextRun* tr = nsnull; + // Don't cache textruns that use spacing + if (!gDisableCache && !aEnableSpacing) { + // Evict first, to make sure that the textrun we return is live. + EvictASCII(); + + TextRunEntry *entry; + nsDependentCSubstring keyStr(aString, aString + aLength); + FontGroupAndCString key(aFontGroup, &keyStr); - gfxTextRun *tr; - TextRunEntry *entry; - FontGroupAndCString key(aFontGroup, &aString); + if (mHashTableASCII.Get(key, &entry)) { + gfxTextRun *cachedTR = entry->textRun; + // Check that this matches what we wanted. If it doesn't, we leave + // this cache entry alone and return a fresh, caller-owned textrun + // below. + if (cachedTR->GetPixelsToAppUnits() == aDevToApp && + cachedTR->IsRightToLeft() == aIsRTL) { + entry->Used(); + tr = cachedTR; + tr->SetContext(aContext); + } + } else { + tr = aFontGroup->MakeTextRun(str, aLength, ¶ms); + entry = new TextRunEntry(tr); + key.Realize(); + mHashTableASCII.Put(key, entry); + } + } - if (mHashTableASCII.Get(key, &entry)) { - entry->Used(); - tr = entry->textRun.get(); + if (tr) { + *aCallerOwns = PR_FALSE; } else { - tr = aFontGroup->MakeTextRun(aString); - entry = new TextRunEntry(tr); - key.Realize(); - mHashTableASCII.Put(key, entry); + // Textrun is not in the cache for some reason. + *aCallerOwns = PR_TRUE; + tr = aFontGroup->MakeTextRun(str, aLength, ¶ms); + } + if (tr) { + // We don't want to have to reconstruct the string + tr->RememberText(str, aLength); } return tr; diff --git a/gfx/thebes/src/gfxWindowsFonts.cpp b/gfx/thebes/src/gfxWindowsFonts.cpp index 71454479281..4f64111c8b0 100644 --- a/gfx/thebes/src/gfxWindowsFonts.cpp +++ b/gfx/thebes/src/gfxWindowsFonts.cpp @@ -513,24 +513,153 @@ gfxWindowsFontGroup::~gfxWindowsFontGroup() } -gfxTextRun * -gfxWindowsFontGroup::MakeTextRun(const nsAString& aString) -{ - if (aString.IsEmpty()) { - NS_WARNING("It is illegal to create a gfxTextRun with empty strings"); - return nsnull; +class gfxWrapperTextRun : public gfxTextRun { +public: + gfxWrapperTextRun(gfxWindowsFontGroup *aGroup, + const PRUint8* aString, PRUint32 aLength, + gfxTextRunFactory::Parameters* aParams) + : gfxTextRun(aParams, PR_TRUE), mContext(aParams->mContext), + mInner(nsDependentCSubstring(reinterpret_cast(aString), + reinterpret_cast(aString + aLength)), + aGroup), + mLength(aLength) + { + mInner.SetRightToLeft(IsRightToLeft()); } - return new gfxWindowsTextRun(aString, this); + gfxWrapperTextRun(gfxWindowsFontGroup *aGroup, + const PRUnichar* aString, PRUint32 aLength, + gfxTextRunFactory::Parameters* aParams) + : gfxTextRun(aParams, PR_TRUE), mContext(aParams->mContext), + mInner(nsDependentSubstring(aString, aString + aLength), aGroup), + mLength(aLength) + { + mInner.SetRightToLeft(IsRightToLeft()); + } + ~gfxWrapperTextRun() {} + + virtual void GetCharFlags(PRUint32 aStart, PRUint32 aLength, + PRUint8* aFlags) + { NS_ERROR("NOT IMPLEMENTED"); } + virtual PRUint8 GetCharFlags(PRUint32 aOffset) + { NS_ERROR("NOT IMPLEMENTED"); for (;;) ; } + virtual PRUint32 GetLength() + { NS_ERROR("NOT IMPLEMENTED"); for (;;) ; } + virtual PRBool SetPotentialLineBreaks(PRUint32 aStart, PRUint32 aLength, + PRPackedBool* aBreakBefore) + { NS_ERROR("NOT IMPLEMENTED"); for (;;) ; } + virtual void DrawToPath(gfxContext *aContext, gfxPoint aPt, + PRUint32 aStart, PRUint32 aLength, + PropertyProvider* aBreakProvider, + gfxFloat* aAdvanceWidth) + { NS_ERROR("NOT IMPLEMENTED"); } + virtual void DrawSpecialString(gfxContext* aContext, gfxPoint aPt, + SpecialString aString) + { NS_ERROR("NOT IMPLEMENTED"); } + virtual Metrics MeasureText(PRUint32 aStart, PRUint32 aLength, + PRBool aTightBoundingBox, + PropertyProvider* aBreakProvider) + { NS_ERROR("NOT IMPLEMENTED"); for (;;) ; } + virtual Metrics MeasureTextSpecialString(SpecialString aString, + PRBool aTightBoundingBox) + { NS_ERROR("NOT IMPLEMENTED"); for (;;) ; } + virtual gfxFloat GetAdvanceWidthSpecialString(SpecialString aString) + { NS_ERROR("NOT IMPLEMENTED"); for (;;) ; } + virtual gfxFont::Metrics GetDecorationMetrics() + { NS_ERROR("NOT IMPLEMENTED"); for (;;) ; } + virtual void SetLineBreaks(PRUint32 aStart, PRUint32 aLength, + PRBool aLineBreakBefore, PRBool aLineBreakAfter, + TextProvider* aProvider, + gfxFloat* aAdvanceWidthDelta) + { NS_ERROR("NOT IMPLEMENTED"); } + virtual PRUint32 BreakAndMeasureText(PRUint32 aStart, PRUint32 aMaxLength, + PRBool aLineBreakBefore, gfxFloat aWidth, + PropertyProvider* aProvider, + PRBool aSuppressInitialBreak, + Metrics* aMetrics, PRBool aTightBoundingBox, + PRBool* aUsedHyphenation, + PRUint32* aLastBreak) + { NS_ERROR("NOT IMPLEMENTED"); for (;;) ; } + virtual void FlushSpacingCache(PRUint32 aStart) + { NS_ERROR("NOT IMPLEMENTED"); } + + virtual void Draw(gfxContext *aContext, gfxPoint aPt, + PRUint32 aStart, PRUint32 aLength, + const gfxRect* aDirtyRect, + PropertyProvider* aBreakProvider, + gfxFloat* aAdvanceWidth); + virtual gfxFloat GetAdvanceWidth(PRUint32 aStart, PRUint32 aLength, + PropertyProvider* aBreakProvider); + + virtual void SetContext(gfxContext* aContext) { mContext = aContext; } + +private: + gfxContext* mContext; + gfxWindowsTextRun mInner; + PRUint32 mLength; + + void SetupSpacingFromProvider(PropertyProvider* aProvider); +}; + +void +gfxWrapperTextRun::SetupSpacingFromProvider(PropertyProvider* aProvider) +{ + if (!(mFlags & gfxTextRunFactory::TEXT_ENABLE_SPACING)) + return; + + NS_ASSERTION(mFlags & gfxTextRunFactory::TEXT_ABSOLUTE_SPACING, + "Can't handle relative spacing"); + + nsAutoTArray spacing; + spacing.AppendElements(mLength); + aProvider->GetSpacing(0, mLength, spacing.Elements()); + + nsTArray spaceArray; + PRUint32 i; + gfxFloat offset = 0; + for (i = 0; i < mLength; ++i) { + NS_ASSERTION(spacing.Elements()[i].mBefore == 0, + "Can't handle before-spacing!"); + gfxFloat nextOffset = offset + spacing.Elements()[i].mAfter/mPixelsToAppUnits; + spaceArray.AppendElement(ROUND(nextOffset) - ROUND(offset)); + offset = nextOffset; + } + mInner.SetSpacing(spaceArray); +} + +void +gfxWrapperTextRun::Draw(gfxContext *aContext, gfxPoint aPt, + PRUint32 aStart, PRUint32 aLength, + const gfxRect* aDirtyRect, + PropertyProvider* aBreakProvider, + gfxFloat* aAdvanceWidth) +{ + NS_ASSERTION(aStart == 0 && aLength == mLength, "Can't handle substrings"); + SetupSpacingFromProvider(aBreakProvider); + gfxPoint pt(aPt.x/mPixelsToAppUnits, aPt.y/mPixelsToAppUnits); + return mInner.Draw(mContext, pt); +} + +gfxFloat +gfxWrapperTextRun::GetAdvanceWidth(PRUint32 aStart, PRUint32 aLength, + PropertyProvider* aBreakProvider) +{ + NS_ASSERTION(aStart == 0 && aLength == mLength, "Can't handle substrings"); + SetupSpacingFromProvider(aBreakProvider); + return mInner.Measure(mContext)*mPixelsToAppUnits; } gfxTextRun * -gfxWindowsFontGroup::MakeTextRun(const nsACString& aString) +gfxWindowsFontGroup::MakeTextRun(const PRUnichar* aString, PRUint32 aLength, + Parameters* aParams) { - if (aString.IsEmpty()) { - NS_WARNING("It is illegal to create a gfxTextRun with empty strings"); - return nsnull; - } - return new gfxWindowsTextRun(aString, this); + return new gfxWrapperTextRun(this, aString, aLength, aParams); +} + +gfxTextRun * +gfxWindowsFontGroup::MakeTextRun(const PRUint8* aString, PRUint32 aLength, + Parameters* aParams) +{ + return new gfxWrapperTextRun(this, aString, aLength, aParams); } /**********************************************************************