Basic bidi support for SVG. Bug 620446, r=roc, a=roc

This commit is contained in:
Simon Montagu 2011-01-04 00:52:17 -08:00
parent 7368bb2f8a
commit 3bd533614c
8 changed files with 258 additions and 15 deletions

View File

@ -36,6 +36,7 @@ EXPORTS = \
gfxTypes.h \
gfxTextRunCache.h \
gfxTextRunWordCache.h \
gfxUnicodeProperties.h \
gfxUtils.h \
gfxUserFontSet.h \
GLDefs.h \

View File

@ -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);

View File

@ -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)
{

View File

@ -510,6 +510,17 @@ public:
*/
nsresult SetPara(const PRUnichar *aText, PRInt32 aLength, nsBidiLevel aParaLevel, nsBidiLevel *aEmbeddingLevels);
/**
* Get the directionality of the text.
*
* @param aDirection receives a <code>NSBIDI_XXX</code> 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
/**
* <code>SetLine</code> sets an <code>nsBidi</code> to
@ -546,17 +557,6 @@ public:
*/
nsresult SetLine(nsIBidi* aParaBidi, PRInt32 aStart, PRInt32 aLimit);
/**
* Get the directionality of the text.
*
* @param aDirection receives a <code>NSBIDI_XXX</code> 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.
*

View File

@ -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;

View File

@ -317,6 +317,24 @@ public:
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 @@ private:
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<nsIFrame*> mLogicalFrames;
nsTArray<nsIFrame*> mVisualFrames;

View File

@ -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)) {

View File

@ -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