mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-30 00:01:50 +00:00
Canvas routines draw right-to-left text backwards - bug 402276 r=smontagu sr=roc
This commit is contained in:
parent
1ed317198b
commit
47e45f54c6
@ -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
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
||||
|
20
layout/reftests/canvas/text-bidi-ltr-ref.html
Normal file
20
layout/reftests/canvas/text-bidi-ltr-ref.html
Normal 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>
|
20
layout/reftests/canvas/text-bidi-ltr-test.html
Normal file
20
layout/reftests/canvas/text-bidi-ltr-test.html
Normal 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>
|
20
layout/reftests/canvas/text-bidi-rtl-ref.html
Normal file
20
layout/reftests/canvas/text-bidi-rtl-ref.html
Normal 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>
|
20
layout/reftests/canvas/text-bidi-rtl-test.html
Normal file
20
layout/reftests/canvas/text-bidi-rtl-test.html
Normal 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>
|
@ -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);
|
||||
|
@ -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);
|
||||
|
Loading…
Reference in New Issue
Block a user