Canvas routines draw right-to-left text backwards - bug 402276 r=smontagu sr=roc

This commit is contained in:
Eric Butler 2008-07-18 11:29:06 -07:00
parent 1ed317198b
commit 47e45f54c6
11 changed files with 471 additions and 191 deletions

View File

@ -65,6 +65,7 @@ REQUIRES = \
imglib2 \
cairo \
thebes \
view \
$(NULL)
# XXX some platforms can't handle building
@ -87,7 +88,9 @@ include $(topsrcdir)/config/rules.mk
CXXFLAGS += $(MOZ_CAIRO_CFLAGS) $(TK_CFLAGS)
INCLUDES += \
-I$(srcdir)/../../../layout/xul/base/src \
-I$(srcdir)/../../../layout/style \
-I$(srcdir)/../../../layout/generic \
$(NULL)
DEFINES += -D_IMPL_NS_LAYOUT

View File

@ -107,6 +107,8 @@
#include "nsFrameManager.h"
#include "nsBidiPresUtils.h"
#ifndef M_PI
#define M_PI 3.14159265358979323846
#define M_PI_2 1.57079632679489661923
@ -312,6 +314,8 @@ NS_INTERFACE_MAP_BEGIN(nsTextMetrics)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
struct nsCanvasBidiProcessor;
/**
** nsCanvasRenderingContext2D
**/
@ -413,19 +417,20 @@ protected:
enum TextDrawOperation {
TEXT_DRAW_OPERATION_FILL,
TEXT_DRAW_OPERATION_STROKE
TEXT_DRAW_OPERATION_STROKE,
TEXT_DRAW_OPERATION_MEASURE
};
/*
* Implementation of the fillText and strokeText functions with the
* operation abstracted to a flag. Follows the HTML 5 spec for rendering
* text. Will query for the fourth, optional argument.
* Implementation of the fillText, strokeText, and measure functions with
* the operation abstracted to a flag.
*/
nsresult drawText(const nsAString& text,
float x,
float y,
float maxWidth,
TextDrawOperation op);
nsresult DrawOrMeasureText(const nsAString& text,
float x,
float y,
float maxWidth,
TextDrawOperation op,
float* aWidth);
// style handling
PRInt32 mLastStyle;
@ -526,6 +531,8 @@ protected:
if (perCSSPixel)
*perCSSPixel = cssPixel;
}
friend struct nsCanvasBidiProcessor;
};
NS_IMPL_ADDREF(nsCanvasRenderingContext2D)
@ -1849,35 +1856,129 @@ TextReplaceWhitespaceCharacters(nsAutoString& str)
NS_IMETHODIMP
nsCanvasRenderingContext2D::FillText(const nsAString& text, float x, float y, float maxWidth)
{
return drawText(text, x, y, maxWidth, TEXT_DRAW_OPERATION_FILL);
return DrawOrMeasureText(text, x, y, maxWidth, TEXT_DRAW_OPERATION_FILL, nsnull);
}
NS_IMETHODIMP
nsCanvasRenderingContext2D::StrokeText(const nsAString& text, float x, float y, float maxWidth)
{
return drawText(text, x, y, maxWidth, TEXT_DRAW_OPERATION_STROKE);
return DrawOrMeasureText(text, x, y, maxWidth, TEXT_DRAW_OPERATION_STROKE, nsnull);
}
nsresult
nsCanvasRenderingContext2D::drawText(const nsAString& rawText,
float x,
float y,
float maxWidth,
TextDrawOperation op)
NS_IMETHODIMP
nsCanvasRenderingContext2D::MeasureText(const nsAString& rawText,
nsIDOMTextMetrics** _retval)
{
if (!FloatValidate(x, y, maxWidth))
float width;
nsresult rv = DrawOrMeasureText(rawText, 0, 0, 0, TEXT_DRAW_OPERATION_MEASURE, &width);
if (NS_FAILED(rv))
return rv;
nsRefPtr<nsIDOMTextMetrics> textMetrics = new nsTextMetrics(width);
if (!textMetrics.get())
return NS_ERROR_OUT_OF_MEMORY;
*_retval = textMetrics.forget().get();
return NS_OK;
}
/**
* Used for nsBidiPresUtils::ProcessText
*/
struct NS_STACK_CLASS nsCanvasBidiProcessor : public nsBidiPresUtils::BidiProcessor
{
virtual void SetText(const PRUnichar* text, PRInt32 length, nsBidiDirection direction)
{
mTextRun = gfxTextRunCache::MakeTextRun(text,
length,
mFontgrp,
mCtx->mThebesContext,
mAppUnitsPerDevPixel,
direction==NSBIDI_RTL ? gfxTextRunFactory::TEXT_IS_RTL : 0);
}
virtual nscoord GetWidth()
{
PRBool tightBoundingBox = PR_FALSE;
gfxTextRun::Metrics textRunMetrics = mTextRun->MeasureText(0,
mTextRun->GetLength(),
tightBoundingBox,
mCtx->mThebesContext,
nsnull);
return static_cast<nscoord>(textRunMetrics.mAdvanceWidth/gfxFloat(mAppUnitsPerDevPixel));
}
virtual void DrawText(nscoord xOffset, nscoord width)
{
gfxPoint point = mPt;
point.x += xOffset * mAppUnitsPerDevPixel;
// offset is given in terms of left side of string
if (mTextRun->IsRightToLeft())
point.x += width * mAppUnitsPerDevPixel;
// stroke or fill the text depending on operation
if (mOp == nsCanvasRenderingContext2D::TEXT_DRAW_OPERATION_STROKE)
mTextRun->DrawToPath(mCtx->mThebesContext,
point,
0,
mTextRun->GetLength(),
nsnull,
nsnull);
else
// mOp == TEXT_DRAW_OPERATION_FILL
mTextRun->Draw(mCtx->mThebesContext,
point,
0,
mTextRun->GetLength(),
nsnull,
nsnull,
nsnull);
}
// current text run
gfxTextRunCache::AutoTextRun mTextRun;
// pointer to the context
nsCanvasRenderingContext2D* mCtx;
// position of the left side of the string, alphabetic baseline
gfxPoint mPt;
// current font
gfxFontGroup* mFontgrp;
// dev pixel conversion factor
PRUint32 mAppUnitsPerDevPixel;
// operation (fill or stroke)
nsCanvasRenderingContext2D::TextDrawOperation mOp;
};
nsresult
nsCanvasRenderingContext2D::DrawOrMeasureText(const nsAString& aRawText,
float aX,
float aY,
float aMaxWidth,
TextDrawOperation aOp,
float* aWidth)
{
nsresult rv;
if (!FloatValidate(aX, aY, aMaxWidth))
return NS_ERROR_DOM_SYNTAX_ERR;
// spec isn't clear on what should happen if maxWidth <= 0, so
// spec isn't clear on what should happen if aMaxWidth <= 0, so
// treat it as an invalid argument
// technically, 0 should be an invalid value as well, but 0 is the default
// arg, and there is no way to tell if the default was used
if (maxWidth < 0)
if (aMaxWidth < 0)
return NS_ERROR_INVALID_ARG;
gfxFontGroup* fontgrp = GetCurrentFontStyle();
NS_ASSERTION(fontgrp, "font group is null");
nsCOMPtr<nsIContent> content = do_QueryInterface(mCanvasElement);
if (!content) {
NS_WARNING("Canvas element must be an nsIContent and non-null");
@ -1890,16 +1991,17 @@ nsCanvasRenderingContext2D::drawText(const nsAString& rawText,
if (!presShell)
return NS_ERROR_FAILURE;
nsBidiPresUtils* bidiUtils = presShell->GetPresContext()->GetBidiUtils();
if (!bidiUtils)
return NS_ERROR_FAILURE;
// replace all the whitespace characters with U+0020 SPACE
nsAutoString textToDraw(rawText);
nsAutoString textToDraw(aRawText);
TextReplaceWhitespaceCharacters(textToDraw);
const PRUnichar* textData;
textToDraw.GetData(&textData);
// for now, default to ltr if not in doc
PRBool isRTL = PR_FALSE;
if (content->IsInDoc()) {
// try to find the closest context
nsRefPtr<nsStyleContext> canvasStyle =
@ -1912,36 +2014,42 @@ nsCanvasRenderingContext2D::drawText(const nsAString& rawText,
NS_STYLE_DIRECTION_RTL;
}
PRUint32 textrunflags = isRTL ? gfxTextRunFactory::TEXT_IS_RTL : 0;
nsCanvasBidiProcessor processor;
// app units conversion factor
PRUint32 aupdp;
GetAppUnitsValues(&aupdp, NULL);
GetAppUnitsValues(&processor.mAppUnitsPerDevPixel, NULL);
processor.mPt = gfxPoint(aX, aY);
processor.mCtx = this;
processor.mOp = aOp;
gfxTextRunCache::AutoTextRun textRun;
textRun = gfxTextRunCache::MakeTextRun(textData,
textToDraw.Length(),
fontgrp,
mThebesContext,
aupdp,
textrunflags);
processor.mFontgrp = GetCurrentFontStyle();
NS_ASSERTION(processor.mFontgrp, "font group is null");
if (!textRun.get())
return NS_ERROR_FAILURE;
nscoord totalWidth;
gfxPoint pt(x, y);
// get the text width
PRBool tightBoundingBox = PR_FALSE;
gfxTextRun::Metrics textRunMetrics = textRun->MeasureText(/* offset = */ 0,
textToDraw.Length(),
tightBoundingBox,
mThebesContext,
nsnull);
gfxFloat textWidth = textRunMetrics.mAdvanceWidth/gfxFloat(aupdp);
// currently calls bidi algo twice since it needs the full width before
// rendering anything. Can probably restructure function to avoid this if
// it's cheaper to store all the runs locally rather than do bidi resolution
// twice.
rv = bidiUtils->ProcessText(textToDraw.get(),
textToDraw.Length(),
isRTL ? NSBIDI_RTL : NSBIDI_LTR,
presShell->GetPresContext(),
processor,
nsBidiPresUtils::MODE_MEASURE,
nsnull,
0,
&totalWidth);
if (NS_FAILED(rv))
return rv;
if (aWidth)
*aWidth = static_cast<float>(totalWidth);
// offset pt x based on text align
// if only measuring, don't need to do any more work
if (aOp==TEXT_DRAW_OPERATION_MEASURE)
return NS_OK;
// offset pt.x based on text align
gfxFloat anchorX;
if (CurrentState().textAlign == TEXT_ALIGN_CENTER)
@ -1953,14 +2061,11 @@ nsCanvasRenderingContext2D::drawText(const nsAString& rawText,
else
anchorX = 1;
if (isRTL)
pt.x += (1 - anchorX) * textWidth;
else
pt.x -= anchorX * textWidth;
processor.mPt.x -= anchorX * totalWidth;
// offset pt y based on text baseline
NS_ASSERTION(fontgrp->FontListLength()>0, "font group contains no fonts");
const gfxFont::Metrics& fontMetrics = fontgrp->GetFontAt(0)->GetMetrics();
// offset pt.y based on text baseline
NS_ASSERTION(processor.mFontgrp->FontListLength()>0, "font group contains no fonts");
const gfxFont::Metrics& fontMetrics = processor.mFontgrp->GetFontAt(0)->GetMetrics();
gfxFloat anchorY;
@ -1973,7 +2078,7 @@ nsCanvasRenderingContext2D::drawText(const nsAString& rawText,
anchorY = 0; // currently unavailable
break;
case TEXT_BASELINE_MIDDLE:
anchorY = (fontMetrics.emAscent-fontMetrics.emDescent)*.5f;
anchorY = (fontMetrics.emAscent - fontMetrics.emDescent) * .5f;
break;
case TEXT_BASELINE_ALPHABETIC:
anchorY = 0;
@ -1989,93 +2094,59 @@ nsCanvasRenderingContext2D::drawText(const nsAString& rawText,
return NS_ERROR_FAILURE;
}
pt.y += anchorY;
processor.mPt.y += anchorY;
// if text is over maxWidth, then scale the text horizonally such that its
// width is precisely maxWidth
if (maxWidth>0 && textWidth > maxWidth) {
cairo_save(mCairo);
processor.mPt.x *= processor.mAppUnitsPerDevPixel;
processor.mPt.y *= processor.mAppUnitsPerDevPixel;
// if text is over aMaxWidth, then scale the text horizontally such that its
// width is precisely aMaxWidth
if (aMaxWidth > 0 && totalWidth > aMaxWidth) {
mThebesContext->Save();
// translate the anchor point to 0, then scale and translate back
cairo_translate(mCairo, x, 0);
cairo_scale(mCairo, (float)(maxWidth/textWidth), 1);
cairo_translate(mCairo, -x, 0);
gfxPoint trans(aX, 0);
mThebesContext->Translate(trans);
mThebesContext->Scale(aMaxWidth/totalWidth, 1);
mThebesContext->Translate(-trans);
}
pt.x *= aupdp;
pt.y *= aupdp;
// stroke or fill depending on operation
if (op == TEXT_DRAW_OPERATION_STROKE) {
cairo_save(mCairo);
cairo_new_path(mCairo);
textRun->DrawToPath(mThebesContext,
pt,
/* offset = */ 0,
textToDraw.Length(),
nsnull,
nsnull);
Stroke();
cairo_restore(mCairo);
} else {
NS_ASSERTION(op == TEXT_DRAW_OPERATION_FILL,
"operation should be FILL or STROKE");
cairo_path_t* old_path;
// back up path if stroking
if (aOp == nsCanvasRenderingContext2D::TEXT_DRAW_OPERATION_STROKE)
old_path = cairo_copy_path(mCairo);
else
ApplyStyle(STYLE_FILL);
textRun->Draw(mThebesContext,
pt,
/* offset = */ 0,
textToDraw.Length(),
nsnull,
nsnull,
nsnull);
rv = bidiUtils->ProcessText(textToDraw.get(),
textToDraw.Length(),
isRTL ? NSBIDI_RTL : NSBIDI_LTR,
presShell->GetPresContext(),
processor,
nsBidiPresUtils::MODE_DRAW,
nsnull,
0,
nsnull);
// stroke and restore path
if (aOp == nsCanvasRenderingContext2D::TEXT_DRAW_OPERATION_STROKE) {
ApplyStyle(STYLE_STROKE);
mThebesContext->Stroke();
cairo_new_path(mCairo);
if (old_path->status == CAIRO_STATUS_SUCCESS && old_path->num_data != 0)
cairo_append_path(mCairo, old_path);
cairo_path_destroy(old_path);
}
// have to restore the context if was modified above
if (maxWidth>0 && textWidth > maxWidth)
cairo_restore(mCairo);
// have to restore the context if was modified for maxWidth
if (aMaxWidth > 0 && totalWidth > aMaxWidth)
mThebesContext->Restore();
return NS_OK;
}
if (NS_FAILED(rv))
return rv;
NS_IMETHODIMP
nsCanvasRenderingContext2D::MeasureText(const nsAString& rawText,
nsIDOMTextMetrics** _retval)
{
// replace all the whitespace characters with U+0020 SPACE
nsAutoString textToMeasure(rawText);
TextReplaceWhitespaceCharacters(textToMeasure);
const PRUnichar* textdata;
textToMeasure.GetData(&textdata);
PRUint32 textrunflags = 0;
PRUint32 aupdp;
GetAppUnitsValues(&aupdp, nsnull);
gfxTextRunCache::AutoTextRun textRun;
textRun = gfxTextRunCache::MakeTextRun(textdata,
textToMeasure.Length(),
GetCurrentFontStyle(),
mThebesContext,
aupdp,
textrunflags);
if(!textRun.get())
return NS_ERROR_FAILURE;
PRBool tightBoundingBox = PR_FALSE;
gfxTextRun::Metrics metrics = textRun->MeasureText(/* offset = */ 0, textToMeasure.Length(),
tightBoundingBox, mThebesContext,
nsnull);
float textWidth = float(metrics.mAdvanceWidth/gfxFloat(aupdp));
nsTextMetrics *textMetrics = new nsTextMetrics(textWidth);
if (!textMetrics)
return NS_ERROR_OUT_OF_MEMORY;
NS_ADDREF(*_retval = textMetrics);
return NS_OK;
return Redraw();
}
NS_IMETHODIMP

View File

@ -1388,10 +1388,8 @@ nsresult nsBidiPresUtils::ProcessText(const PRUnichar* aText,
PRInt32 aLength,
nsBidiDirection aBaseDirection,
nsPresContext* aPresContext,
nsIRenderingContext& aRenderingContext,
BidiProcessor& aprocessor,
Mode aMode,
nscoord aX,
nscoord aY,
nsBidiPositionResolve* aPosResolve,
PRInt32 aPosResolveCount,
nscoord* aWidth)
@ -1410,18 +1408,15 @@ nsresult nsBidiPresUtils::ProcessText(const PRUnichar* aText,
if (NS_FAILED(rv))
return rv;
nscoord width, xEndRun, xStartText = aX;
PRBool isRTL = PR_FALSE;
nscoord xOffset = 0;
nscoord width, xEndRun;
nscoord totalWidth = 0;
PRInt32 i, start, limit, length;
PRUint32 visualStart = 0;
PRUint8 charType;
PRUint8 prevType = eCharType_LeftToRight;
nsBidiLevel level;
PRUint32 hints = 0;
aRenderingContext.GetHints(hints);
PRBool isBidiSystem = !!(hints & NS_RENDERING_HINT_BIDI_REORDERING);
PRBool isBidiSystem = PR_TRUE;
for(int nPosResolve=0; nPosResolve < aPosResolveCount; ++nPosResolve)
{
@ -1447,35 +1442,26 @@ nsresult nsBidiPresUtils::ProcessText(const PRUnichar* aText,
/*
* If |level| is even, i.e. the direction of the run is left-to-right, we
* render the subruns from left to right and increment the x-coordinate
* |aX| by the width of each subrun after rendering.
* |xOffset| by the width of each subrun after rendering.
*
* If |level| is odd, i.e. the direction of the run is right-to-left, we
* render the subruns from right to left. We begin by incrementing |aX| by
* render the subruns from right to left. We begin by incrementing |xOffset| by
* the width of the whole run, and then decrement it by the width of each
* subrun before rendering. After rendering all the subruns, we restore the
* x-coordinate of the end of the run for the start of the next run.
*/
aRenderingContext.SetTextRunRTL(level & 1);
if (level & 1) {
aRenderingContext.GetWidth(aText + start, subRunLength, width, nsnull);
aX += width;
xEndRun = aX;
aprocessor.SetText(aText + start, subRunLength, nsBidiDirection(level & 1));
width = aprocessor.GetWidth();
xOffset += width;
xEndRun = xOffset;
}
while (subRunCount > 0) {
// CalculateCharType can increment subRunCount if the run
// contains mixed character types
CalculateCharType(lineOffset, typeLimit, subRunLimit, subRunLength, subRunCount, charType, prevType);
if (eCharType_RightToLeftArabic == charType) {
isBidiSystem = !!(hints & NS_RENDERING_HINT_ARABIC_SHAPING);
}
if (isBidiSystem && (CHARTYPE_IS_RTL(charType) ^ isRTL) ) {
// set reading order into DC
isRTL = !isRTL;
aRenderingContext.SetRightToLeftText(isRTL);
}
nsAutoString runVisualText;
runVisualText.Assign(aText + start, subRunLength);
@ -1483,15 +1469,16 @@ nsresult nsBidiPresUtils::ProcessText(const PRUnichar* aText,
return NS_ERROR_OUT_OF_MEMORY;
FormatUnicodeText(aPresContext, runVisualText.BeginWriting(), subRunLength,
(nsCharType)charType, level & 1,
isBidiSystem, (hints & NS_RENDERING_HINT_NEW_TEXT_RUNS) != 0);
isBidiSystem, PR_TRUE);
aRenderingContext.GetWidth(runVisualText.get(), subRunLength, width, nsnull);
aprocessor.SetText(runVisualText.get(), subRunLength, nsBidiDirection(level & 1));
width = aprocessor.GetWidth();
totalWidth += width;
if (level & 1) {
aX -= width;
xOffset -= width;
}
if (aMode == MODE_DRAW) {
aRenderingContext.DrawString(runVisualText.get(), subRunLength, aX, aY);
aprocessor.DrawText(xOffset, width);
}
/*
@ -1515,11 +1502,11 @@ nsresult nsBidiPresUtils::ProcessText(const PRUnichar* aText,
/*
* If this run is only one character long, we have an easy case:
* the visual position is the x-coord of the start of the run
* less the x-coord of the start of the whole text (saved in xStartText).
* less the x-coord of the start of the whole text.
*/
if (subRunLength == 1) {
posResolve->visualIndex = visualStart;
posResolve->visualLeftTwips = aX - xStartText;
posResolve->visualLeftTwips = xOffset;
}
/*
* Otherwise, we need to measure the width of the run's part
@ -1547,16 +1534,15 @@ nsresult nsBidiPresUtils::ProcessText(const PRUnichar* aText,
}
// The delta between the start of the run and the left part's end.
PRInt32 visualLeftLength = posResolve->visualIndex - visualStart;
aRenderingContext.GetWidth(visualLeftPart,
visualLeftLength,
subWidth, nsnull);
posResolve->visualLeftTwips = aX + subWidth - xStartText;
aprocessor.SetText(visualLeftPart, visualLeftLength, nsBidiDirection(level & 1));
subWidth = aprocessor.GetWidth();
posResolve->visualLeftTwips = xOffset + subWidth;
}
}
}
if (!(level & 1)) {
aX += width;
xOffset += width;
}
--subRunCount;
@ -1565,22 +1551,77 @@ nsresult nsBidiPresUtils::ProcessText(const PRUnichar* aText,
subRunLength = typeLimit - lineOffset;
} // while
if (level & 1) {
aX = xEndRun;
xOffset = xEndRun;
}
visualStart += length;
} // for
// Restore original reading order
if (isRTL) {
aRenderingContext.SetRightToLeftText(PR_FALSE);
}
if (aWidth) {
*aWidth = totalWidth;
}
return NS_OK;
}
class NS_STACK_CLASS nsIRenderingContextBidiProcessor : public nsBidiPresUtils::BidiProcessor {
public:
nsIRenderingContextBidiProcessor(nsIRenderingContext* aCtx,
const nsPoint& aPt)
: mCtx(aCtx), mPt(aPt) { }
~nsIRenderingContextBidiProcessor()
{
mCtx->SetRightToLeftText(PR_FALSE);
}
virtual void SetText(const PRUnichar* aText,
PRInt32 aLength,
nsBidiDirection aDirection)
{
mCtx->SetTextRunRTL(aDirection==NSBIDI_RTL);
mText = aText;
mLength = aLength;
}
virtual nscoord GetWidth()
{
nscoord width;
mCtx->GetWidth(mText, mLength, width, nsnull);
return width;
}
virtual void DrawText(nscoord aXOffset,
nscoord)
{
mCtx->DrawString(mText, mLength, mPt.x + aXOffset, mPt.y);
}
private:
nsPoint mPt;
nsIRenderingContext* mCtx;
const PRUnichar* mText;
PRInt32 mLength;
nsBidiDirection mDirection;
};
nsresult nsBidiPresUtils::ProcessTextForRenderingContext(const PRUnichar* aText,
PRInt32 aLength,
nsBidiDirection aBaseDirection,
nsPresContext* aPresContext,
nsIRenderingContext& aRenderingContext,
Mode aMode,
nscoord aX,
nscoord aY,
nsBidiPositionResolve* aPosResolve,
PRInt32 aPosResolveCount,
nscoord* aWidth)
{
nsIRenderingContextBidiProcessor processor(&aRenderingContext, nsPoint(aX, aY));
return ProcessText(aText, aLength, aBaseDirection, aPresContext, processor,
aMode, aPosResolve, aPosResolveCount, aWidth);
}
nsresult
nsBidiPresUtils::ReorderUnicodeText(PRUnichar* aText,
PRInt32& aTextLength,

View File

@ -113,6 +113,52 @@ public:
~nsBidiPresUtils();
PRBool IsSuccessful(void) const;
/**
* Interface for the processor used by ProcessText. Used by process text to
* collect information about the width of subruns and to notify where each
* subrun should be rendered.
*/
class BidiProcessor {
public:
virtual ~BidiProcessor() { }
/**
* Sets the current text with the given length and the given direction.
*
* @remark The reason that the function gives a string instead of an index
* is that ProcessText copies and modifies the string passed to it, so
* passing an index would be impossible.
*
* @param aText The string of text.
* @param aLength The length of the string of text.
* @param aDirection The direction of the text. The string will never have
* mixed direction.
*/
virtual void SetText(const PRUnichar* aText,
PRInt32 aLength,
nsBidiDirection aDirection) = 0;
/**
* Returns the measured width of the text given in SetText. If SetText was
* not called with valid parameters, the result of this call is undefined.
* This call is guaranteed to only be called once between SetText calls.
* Will be invoked before DrawText.
*/
virtual nscoord GetWidth() = 0;
/**
* Draws the text given in SetText to a rendering context. If SetText was
* not called with valid parameters, the result of this call is undefined.
* This call is guaranteed to only be called once between SetText calls.
*
* @param aXOffset The offset of the left side of the substring to be drawn
* from the beginning of the overall string passed to ProcessText.
* @param aWidth The width returned by GetWidth.
*/
virtual void DrawText(nscoord aXOffset,
nscoord aWidth) = 0;
};
/**
* Make Bidi engine calculate the embedding levels of the frames that are
* descendants of a given block frame.
@ -194,8 +240,8 @@ public:
nsBidiPositionResolve* aPosResolve = nsnull,
PRInt32 aPosResolveCount = 0)
{
return ProcessText(aText, aLength, aBaseDirection, aPresContext, aRenderingContext,
MODE_DRAW, aX, aY, aPosResolve, aPosResolveCount, nsnull);
return ProcessTextForRenderingContext(aText, aLength, aBaseDirection, aPresContext, aRenderingContext,
MODE_DRAW, aX, aY, aPosResolve, aPosResolveCount, nsnull);
}
nscoord MeasureTextWidth(const PRUnichar* aText,
@ -205,8 +251,8 @@ public:
nsIRenderingContext& aRenderingContext)
{
nscoord length;
nsresult rv = ProcessText(aText, aLength, aBaseDirection, aPresContext, aRenderingContext,
MODE_MEASURE, 0, 0, nsnull, 0, &length);
nsresult rv = ProcessTextForRenderingContext(aText, aLength, aBaseDirection, aPresContext, aRenderingContext,
MODE_MEASURE, 0, 0, nsnull, 0, &length);
return NS_SUCCEEDED(rv) ? length : 0;
}
@ -255,19 +301,50 @@ public:
*/
static nsBidiLevel GetFrameBaseLevel(nsIFrame* aFrame);
private:
enum Mode { MODE_DRAW, MODE_MEASURE };
/**
* Reorder plain text using the Unicode Bidi algorithm and send it to
* a processor for rendering or measuring
*
* @param[in] aText the string to be processed (in logical order)
* @param aLength the number of characters in the string
* @param aBaseDirection the base direction of the string
* NSBIDI_LTR - left-to-right string
* NSBIDI_RTL - right-to-left string
* @param aPresContext the presentation context
* @param aprocessor the bidi processor
* @param aMode the operation to process
* MODE_DRAW - invokes DrawText on the processor for each substring
* MODE_MEASURE - does not invoke DrawText on the processor
* Note that the string is always measured, regardless of mode
* @param[in,out] aPosResolve array of logical positions to resolve into
* visual positions; can be nsnull if this functionality is not required
* @param aPosResolveCount number of items in the aPosResolve array
* @param[out] aWidth Pointer to where the width will be stored (may be null)
*/
nsresult ProcessText(const PRUnichar* aText,
PRInt32 aLength,
nsBidiDirection aBaseDirection,
nsPresContext* aPresContext,
nsIRenderingContext& aRenderingContext,
BidiProcessor& aprocessor,
Mode aMode,
nscoord aX, // DRAW only
nscoord aY, // DRAW only
nsBidiPositionResolve* aPosResolve, /* may be null */
nsBidiPositionResolve* aPosResolve,
PRInt32 aPosResolveCount,
nscoord* aWidth /* may be null */);
nscoord* aWidth);
private:
nsresult ProcessTextForRenderingContext(const PRUnichar* aText,
PRInt32 aLength,
nsBidiDirection aBaseDirection,
nsPresContext* aPresContext,
nsIRenderingContext& aRenderingContext,
Mode aMode,
nscoord aX, // DRAW only
nscoord aY, // DRAW only
nsBidiPositionResolve* aPosResolve, /* may be null */
PRInt32 aPosResolveCount,
nscoord* aWidth /* may be null */);
/**
* Create a string containing entire text content of this block.

View File

@ -10,7 +10,7 @@
== text-rtl-end.html text-rtl-left.html
!= text-rtl-left.html text-rtl-right.html
!= text-ltr-left.html text-rtl-left.html
== text-ltr-left.html text-rtl-left.html
== text-ltr-alignment-test.html text-ltr-alignment-ref.html
== text-rtl-alignment-test.html text-rtl-alignment-ref.html
@ -29,3 +29,7 @@
== text-no-frame-2-test.html text-not-in-doc-ref.html
== text-not-in-doc-test.html text-not-in-doc-ref.html
# weird pixel-rounding on mac prevents these tests from passing
fails-if(MOZ_WIDGET_TOOLKIT=="cocoa") == text-bidi-ltr-test.html text-bidi-ltr-ref.html
fails-if(MOZ_WIDGET_TOOLKIT=="cocoa") == text-bidi-rtl-test.html text-bidi-rtl-ref.html

View File

@ -0,0 +1,20 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test to ensure bidi is resolved correctly</title>
</head>
<body>
<canvas id="c" width="256" height="64" style="direction:ltr"></canvas>
<script type="text/javascript">
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
var str = "hello\u202D\u05DD\u05D5\u05DC\u05E9\u202Cgoodbye";
ctx.fillStyle = 'black';
ctx.font = '10px sans-serif';
ctx.fillText(str, 128, 32);
</script>
</body>
</html>

View File

@ -0,0 +1,20 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test to ensure bidi is resolved correctly</title>
</head>
<body>
<canvas id="c" width="256" height="64" style="direction:ltr"></canvas>
<script type="text/javascript">
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
var str = "hello\u05E9\u05DC\u05D5\u05DDgoodbye";
ctx.fillStyle = 'black';
ctx.font = '10px sans-serif';
ctx.fillText(str, 128, 32);
</script>
</body>
</html>

View File

@ -0,0 +1,20 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test to ensure bidi is resolved correctly</title>
</head>
<body>
<canvas id="c" width="256" height="64" style="direction:rtl"></canvas>
<script type="text/javascript">
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
var str = "goodbye\u202D\u05DD\u05D5\u05DC\u05E9\u202Chello";
ctx.fillStyle = 'black';
ctx.font = '10px sans-serif';
ctx.fillText(str, 128, 32);
</script>
</body>
</html>

View File

@ -0,0 +1,20 @@
<!DOCTYPE HTML>
<html>
<head>
<title>Test to ensure bidi is resolved correctly</title>
</head>
<body>
<canvas id="c" width="256" height="64" style="direction:rtl"></canvas>
<script type="text/javascript">
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
var str = "hello\u05E9\u05DC\u05D5\u05DDgoodbye";
ctx.fillStyle = 'black';
ctx.font = '10px sans-serif';
ctx.fillText(str, 128, 32);
</script>
</body>
</html>

View File

@ -9,13 +9,15 @@
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.strokeStyle = 'black';
ctx.beginPath();
ctx.moveTo(0,32);
ctx.lineTo(128,32);
ctx.stroke();
ctx.fillStyle = 'white';
ctx.font = '20px sans-serif';
ctx.textBaseline = 'bottom';
ctx.fillText('TEXT', 64, 32);

View File

@ -9,13 +9,15 @@
var canvas = document.getElementById('c');
var ctx = canvas.getContext('2d');
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, canvas.width, canvas.height)
ctx.strokeStyle = 'black';
ctx.beginPath();
ctx.moveTo(0,32);
ctx.lineTo(128,32);
ctx.stroke();
ctx.fillStyle = 'white';
ctx.font = '20px sans-serif';
ctx.textBaseline = 'top';
ctx.fillText('TEXT', 64, 32);