diff --git a/gfx/thebes/Makefile.in b/gfx/thebes/Makefile.in index 632938e11eb4..73ef37c66035 100644 --- a/gfx/thebes/Makefile.in +++ b/gfx/thebes/Makefile.in @@ -36,6 +36,7 @@ EXPORTS = \ gfxTypes.h \ gfxTextRunCache.h \ gfxTextRunWordCache.h \ + gfxUnicodeProperties.h \ gfxUtils.h \ gfxUserFontSet.h \ GLDefs.h \ diff --git a/gfx/thebes/gfxUnicodeProperties.h b/gfx/thebes/gfxUnicodeProperties.h index b2574af64040..2224f2e129e7 100644 --- a/gfx/thebes/gfxUnicodeProperties.h +++ b/gfx/thebes/gfxUnicodeProperties.h @@ -39,8 +39,9 @@ #define GFX_UNICODEPROPERTIES_H #include "prtypes.h" +#include "gfxTypes.h" -class gfxUnicodeProperties +class THEBES_API gfxUnicodeProperties { public: static PRUint32 GetMirroredChar(PRUint32 aCh); diff --git a/layout/base/nsBidi.cpp b/layout/base/nsBidi.cpp index 175a1a240363..dd99ce9e2b47 100644 --- a/layout/base/nsBidi.cpp +++ b/layout/base/nsBidi.cpp @@ -1116,15 +1116,15 @@ void nsBidi::AdjustWSLevels() } } } -#ifdef FULL_BIDI_ENGINE - -/* -------------------------------------------------------------------------- */ nsresult nsBidi::GetDirection(nsBidiDirection* aDirection) { *aDirection = mDirection; return NS_OK; } +#ifdef FULL_BIDI_ENGINE + +/* -------------------------------------------------------------------------- */ nsresult nsBidi::GetLength(PRInt32* aLength) { diff --git a/layout/base/nsBidi.h b/layout/base/nsBidi.h index f9462aa4b81e..4d2dc4276d36 100644 --- a/layout/base/nsBidi.h +++ b/layout/base/nsBidi.h @@ -510,6 +510,17 @@ class nsBidi */ nsresult SetPara(const PRUnichar *aText, PRInt32 aLength, nsBidiLevel aParaLevel, nsBidiLevel *aEmbeddingLevels); + /** + * Get the directionality of the text. + * + * @param aDirection receives a NSBIDI_XXX value that indicates if the entire text + * represented by this object is unidirectional, + * and which direction, or if it is mixed-directional. + * + * @see nsBidiDirection + */ + nsresult GetDirection(nsBidiDirection* aDirection); + #ifdef FULL_BIDI_ENGINE /** * SetLine sets an nsBidi to @@ -546,17 +557,6 @@ class nsBidi */ nsresult SetLine(nsIBidi* aParaBidi, PRInt32 aStart, PRInt32 aLimit); - /** - * Get the directionality of the text. - * - * @param aDirection receives a NSBIDI_XXX value that indicates if the entire text - * represented by this object is unidirectional, - * and which direction, or if it is mixed-directional. - * - * @see nsBidiDirection - */ - nsresult GetDirection(nsBidiDirection* aDirection); - /** * Get the length of the text. * diff --git a/layout/base/nsBidiPresUtils.cpp b/layout/base/nsBidiPresUtils.cpp index 3823f017ced0..fd69e29166d6 100644 --- a/layout/base/nsBidiPresUtils.cpp +++ b/layout/base/nsBidiPresUtils.cpp @@ -55,6 +55,7 @@ #include "nsPlaceholderFrame.h" #include "nsContainerFrame.h" #include "nsFirstLetterFrame.h" +#include "gfxUnicodeProperties.h" using namespace mozilla; @@ -1683,6 +1684,139 @@ nsresult nsBidiPresUtils::ProcessTextForRenderingContext(const PRUnichar* aMode, aPosResolve, aPosResolveCount, aWidth); } +/* static */ +void nsBidiPresUtils::WriteReverse(const PRUnichar* aSrc, + PRUint32 aSrcLength, + PRUnichar* aDest) +{ + const PRUnichar* src = aSrc + aSrcLength; + PRUnichar* dest = aDest; + PRUint32 UTF32Char; + + while (--src >= aSrc) { + if (NS_IS_LOW_SURROGATE(*src)) { + if (src > aSrc && NS_IS_HIGH_SURROGATE(*(src - 1))) { + UTF32Char = SURROGATE_TO_UCS4(*(src - 1), *src); + --src; + } else { + UTF32Char = UCS2_REPLACEMENT_CHAR; + } + } else if (NS_IS_HIGH_SURROGATE(*src)) { + // paired high surrogates are handled above, so this is a lone high surrogate + UTF32Char = UCS2_REPLACEMENT_CHAR; + } else { + UTF32Char = *src; + } + + UTF32Char = gfxUnicodeProperties::GetMirroredChar(UTF32Char); + + if (IS_IN_BMP(UTF32Char)) { + *(dest++) = UTF32Char; + } else { + *(dest++) = H_SURROGATE(UTF32Char); + *(dest++) = L_SURROGATE(UTF32Char); + } + } + + NS_ASSERTION(dest - aDest == aSrcLength, "Whole string not copied"); +} + +/* static */ +PRBool nsBidiPresUtils::WriteLogicalToVisual(const PRUnichar* aSrc, + PRUint32 aSrcLength, + PRUnichar* aDest, + nsBidiLevel aBaseDirection, + nsBidi* aBidiEngine) +{ + const PRUnichar* src = aSrc; + nsresult rv = aBidiEngine->SetPara(src, aSrcLength, aBaseDirection, nsnull); + if (NS_FAILED(rv)) { + return PR_FALSE; + } + + nsBidiDirection dir; + rv = aBidiEngine->GetDirection(&dir); + // NSBIDI_LTR returned from GetDirection means the whole text is LTR + if (NS_FAILED(rv) || dir == NSBIDI_LTR) { + return PR_FALSE; + } + + PRInt32 runCount; + rv = aBidiEngine->CountRuns(&runCount); + if (NS_FAILED(rv)) { + return PR_FALSE; + } + + PRInt32 runIndex, start, length; + PRUnichar* dest = aDest; + + for (runIndex = 0; runIndex < runCount; ++runIndex) { + rv = aBidiEngine->GetVisualRun(runIndex, &start, &length, &dir); + if (NS_FAILED(rv)) { + return PR_FALSE; + } + + src = aSrc + start; + + if (dir == NSBIDI_RTL) { + WriteReverse(src, length, dest); + dest += length; + } else { + do { + NS_ASSERTION(src >= aSrc && src < aSrc + aSrcLength, + "logical index out of range"); + NS_ASSERTION(dest < aDest + aSrcLength, "visual index out of range"); + *(dest++) = *(src++); + } while (--length); + } + } + + NS_ASSERTION(dest - aDest == aSrcLength, "whole string not copied"); + return PR_TRUE; +} + +void nsBidiPresUtils::CopyLogicalToVisual(const nsAString& aSource, + nsAString& aDest, + nsBidiLevel aBaseDirection, + PRBool aOverride) +{ + aDest.SetLength(0); + PRUint32 srcLength = aSource.Length(); + if (srcLength == 0) + return; + if (!EnsureStringLength(aDest, srcLength)) { + return; + } + nsAString::const_iterator fromBegin, fromEnd; + nsAString::iterator toBegin; + aSource.BeginReading(fromBegin); + aSource.EndReading(fromEnd); + aDest.BeginWriting(toBegin); + + if (aOverride) { + if (aBaseDirection == NSBIDI_RTL) { + // no need to use the converter -- just copy the string in reverse order + WriteReverse(fromBegin.get(), srcLength, toBegin.get()); + } else { + // if aOverride && aBaseDirection == NSBIDI_LTR, fall through to the + // simple copy + aDest.SetLength(0); + } + } else { + if (!WriteLogicalToVisual(fromBegin.get(), srcLength, toBegin.get(), + aBaseDirection, mBidiEngine)) { + aDest.SetLength(0); + } + } + + if (aDest.IsEmpty()) { + // Either there was an error or the source is unidirectional + // left-to-right. In either case, just copy source to dest. + CopyUnicodeTo(aSource.BeginReading(fromBegin), aSource.EndReading(fromEnd), + aDest); + } +} + PRUint32 nsBidiPresUtils::EstimateMemoryUsed() { PRUint32 size = 0; diff --git a/layout/base/nsBidiPresUtils.h b/layout/base/nsBidiPresUtils.h index c0b9382a2f44..e8e24c83091e 100644 --- a/layout/base/nsBidiPresUtils.h +++ b/layout/base/nsBidiPresUtils.h @@ -317,6 +317,24 @@ class nsBidiPresUtils { PRInt32 aPosResolveCount, nscoord* aWidth); + /** + * Make a copy of a string, converting from logical to visual order + * + * @param aSource the source string + * @param aDest the destination string + * @param aBaseDirection the base direction of the string + * (NSBIDI_LTR or NSBIDI_RTL to force the base direction; + * NSBIDI_DEFAULT_LTR or NSBIDI_DEFAULT_RTL to let the bidi engine + * determine the direction from rules P2 and P3 of the bidi algorithm. + * @see nsBidi::GetPara + * @param aOverride if TRUE, the text has a bidi override, according to + * the direction in aDir + */ + void CopyLogicalToVisual(const nsAString& aSource, + nsAString& aDest, + nsBidiLevel aBaseDirection, + PRBool aOverride); + /** * Guess at how much memory is being used by this nsBidiPresUtils instance, * including memory used by nsBidi. @@ -477,6 +495,17 @@ class nsBidiPresUtils { void StripBidiControlCharacters(PRUnichar* aText, PRInt32& aTextLength) const; + + static PRBool WriteLogicalToVisual(const PRUnichar* aSrc, + PRUint32 aSrcLength, + PRUnichar* aDest, + nsBidiLevel aBaseDirection, + nsBidi* aBidiEngine); + + static void WriteReverse(const PRUnichar* aSrc, + PRUint32 aSrcLength, + PRUnichar* aDest); + nsAutoString mBuffer; nsTArray mLogicalFrames; nsTArray mVisualFrames; diff --git a/layout/svg/base/src/nsSVGGlyphFrame.cpp b/layout/svg/base/src/nsSVGGlyphFrame.cpp index 88fc2207d481..2bde6669303e 100644 --- a/layout/svg/base/src/nsSVGGlyphFrame.cpp +++ b/layout/svg/base/src/nsSVGGlyphFrame.cpp @@ -39,6 +39,7 @@ #include "nsSVGTextFrame.h" #include "nsILookAndFeel.h" #include "nsTextFragment.h" +#include "nsBidiPresUtils.h" #include "nsSVGUtils.h" #include "SVGLengthList.h" #include "nsIDOMSVGLength.h" @@ -1548,6 +1549,50 @@ nsSVGGlyphFrame::EnsureTextRun(float *aDrawScale, float *aMetricsScale, if (!GetCharacterData(text)) return PR_FALSE; + nsBidiPresUtils* bidiUtils = presContext->GetBidiUtils(); + if (bidiUtils) { + nsAutoString visualText; + + /* + * XXXsmontagu: The SVG spec says: + * + * http://www.w3.org/TR/SVG11/text.html#DirectionProperty + * "For the 'direction' property to have any effect, the 'unicode-bidi' + * property's value must be embed or bidi-override." + * + * The SVGTiny spec, on the other hand, says + * + * http://www.w3.org/TR/SVGTiny12/text.html#DirectionProperty + * "For the 'direction' property to have any effect on an element that + * does not by itself establish a new text chunk (such as the 'tspan' + * element in SVG 1.2 Tiny), the 'unicode-bidi' property's value must + * be embed or bidi-override." + * + * Note that this is different from HTML/CSS, where setting the 'dir' + * attribute on an inline element automatically sets unicode-bidi: embed + * + * Our current implementation of bidi in SVG does not distinguish between + * different text elements, but treats every text container frame as a + * new text chunk, so we always set the base direction according to the + * direction property + * + * See also XXXsmontagu comments in nsSVGTextFrame::UpdateGlyphPositioning + */ + + // Get the unicodeBidi property from the parent, because it doesn't + // inherit + PRBool bidiOverride = (mParent->GetStyleTextReset()->mUnicodeBidi == + NS_STYLE_UNICODE_BIDI_OVERRIDE); + nsBidiLevel baseDirection = + GetStyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL ? + NSBIDI_RTL : NSBIDI_LTR; + bidiUtils->CopyLogicalToVisual(text, visualText, + baseDirection, bidiOverride); + if (!visualText.IsEmpty()) { + text = visualText; + } + } + gfxMatrix m; if (aForceGlobalTransform || !(GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD)) { diff --git a/layout/svg/base/src/nsSVGTextFrame.cpp b/layout/svg/base/src/nsSVGTextFrame.cpp index ce73a1f8c1ba..7cc6bca2b984 100644 --- a/layout/svg/base/src/nsSVGTextFrame.cpp +++ b/layout/svg/base/src/nsSVGTextFrame.cpp @@ -341,6 +341,39 @@ nsSVGTextFrame::UpdateGlyphPositioning(PRBool aForceGlobalTransform) PRUint8 anchor = firstFragment->GetTextAnchor(); + /** + * XXXsmontagu: The SVG spec is very vague as to how 'text-anchor' + * interacts with bidirectional text. It says: + * + * "For scripts that are inherently right to left such as Hebrew and + * Arabic [text-anchor: start] is equivalent to right alignment." + * and + * "For scripts that are inherently right to left such as Hebrew and + * Arabic, [text-anchor: end] is equivalent to left alignment. + * + * It's not clear how this should be implemented in terms of defined + * properties, i.e. how one should determine that a particular element + * contains a script that is inherently right to left. + * + * The code below follows http://www.w3.org/TR/SVGTiny12/text.html#TextAnchorProperty + * and swaps the values of text-anchor: end and text-anchor: start + * whenever the 'direction' property is rtl. + * + * This is probably the "right" thing to do, but other browsers don't do it, + * so I am leaving it inside #if 0 for now for interoperability. + * + * See also XXXsmontagu comments in nsSVGGlyphFrame::EnsureTextRun + */ +#if 0 + if (GetStyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) { + if (anchor == NS_STYLE_TEXT_ANCHOR_END) { + anchor = NS_STYLE_TEXT_ANCHOR_START; + } else if (anchor == NS_STYLE_TEXT_ANCHOR_START) { + anchor = NS_STYLE_TEXT_ANCHOR_END; + } + } +#endif + float chunkLength = 0.0f; if (anchor != NS_STYLE_TEXT_ANCHOR_START) { // need to get the total chunk length