From 47e45f54c6b2e90fa77a8f908cf5dc967bf94382 Mon Sep 17 00:00:00 2001 From: Eric Butler Date: Fri, 18 Jul 2008 11:29:06 -0700 Subject: [PATCH] Canvas routines draw right-to-left text backwards - bug 402276 r=smontagu sr=roc --- content/canvas/src/Makefile.in | 3 + .../canvas/src/nsCanvasRenderingContext2D.cpp | 345 +++++++++++------- layout/base/nsBidiPresUtils.cpp | 123 ++++--- layout/base/nsBidiPresUtils.h | 97 ++++- layout/reftests/canvas/reftest.list | 6 +- layout/reftests/canvas/text-bidi-ltr-ref.html | 20 + .../reftests/canvas/text-bidi-ltr-test.html | 20 + layout/reftests/canvas/text-bidi-rtl-ref.html | 20 + .../reftests/canvas/text-bidi-rtl-test.html | 20 + .../canvas/text-horzline-with-bottom.html | 4 +- .../canvas/text-horzline-with-top.html | 4 +- 11 files changed, 471 insertions(+), 191 deletions(-) create mode 100644 layout/reftests/canvas/text-bidi-ltr-ref.html create mode 100644 layout/reftests/canvas/text-bidi-ltr-test.html create mode 100644 layout/reftests/canvas/text-bidi-rtl-ref.html create mode 100644 layout/reftests/canvas/text-bidi-rtl-test.html diff --git a/content/canvas/src/Makefile.in b/content/canvas/src/Makefile.in index 8b947f241be5..0717b0fe4209 100644 --- a/content/canvas/src/Makefile.in +++ b/content/canvas/src/Makefile.in @@ -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 diff --git a/content/canvas/src/nsCanvasRenderingContext2D.cpp b/content/canvas/src/nsCanvasRenderingContext2D.cpp index 27b181e49aea..b3ced1e03c7d 100644 --- a/content/canvas/src/nsCanvasRenderingContext2D.cpp +++ b/content/canvas/src/nsCanvasRenderingContext2D.cpp @@ -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 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(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 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 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(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 diff --git a/layout/base/nsBidiPresUtils.cpp b/layout/base/nsBidiPresUtils.cpp index 2b610e47efd2..01e0b860745e 100644 --- a/layout/base/nsBidiPresUtils.cpp +++ b/layout/base/nsBidiPresUtils.cpp @@ -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, diff --git a/layout/base/nsBidiPresUtils.h b/layout/base/nsBidiPresUtils.h index a743120bc19e..be1e8ccd0f86 100644 --- a/layout/base/nsBidiPresUtils.h +++ b/layout/base/nsBidiPresUtils.h @@ -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. diff --git a/layout/reftests/canvas/reftest.list b/layout/reftests/canvas/reftest.list index 310ec4849781..e3656f76299e 100644 --- a/layout/reftests/canvas/reftest.list +++ b/layout/reftests/canvas/reftest.list @@ -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 + diff --git a/layout/reftests/canvas/text-bidi-ltr-ref.html b/layout/reftests/canvas/text-bidi-ltr-ref.html new file mode 100644 index 000000000000..524be1edd43a --- /dev/null +++ b/layout/reftests/canvas/text-bidi-ltr-ref.html @@ -0,0 +1,20 @@ + + + +Test to ensure bidi is resolved correctly + + + + + + diff --git a/layout/reftests/canvas/text-bidi-ltr-test.html b/layout/reftests/canvas/text-bidi-ltr-test.html new file mode 100644 index 000000000000..9e263b9d0c9d --- /dev/null +++ b/layout/reftests/canvas/text-bidi-ltr-test.html @@ -0,0 +1,20 @@ + + + +Test to ensure bidi is resolved correctly + + + + + + diff --git a/layout/reftests/canvas/text-bidi-rtl-ref.html b/layout/reftests/canvas/text-bidi-rtl-ref.html new file mode 100644 index 000000000000..c264e7104ef1 --- /dev/null +++ b/layout/reftests/canvas/text-bidi-rtl-ref.html @@ -0,0 +1,20 @@ + + + +Test to ensure bidi is resolved correctly + + + + + + diff --git a/layout/reftests/canvas/text-bidi-rtl-test.html b/layout/reftests/canvas/text-bidi-rtl-test.html new file mode 100644 index 000000000000..a444aca32254 --- /dev/null +++ b/layout/reftests/canvas/text-bidi-rtl-test.html @@ -0,0 +1,20 @@ + + + +Test to ensure bidi is resolved correctly + + + + + + diff --git a/layout/reftests/canvas/text-horzline-with-bottom.html b/layout/reftests/canvas/text-horzline-with-bottom.html index da6b6062b55e..2d7a23363406 100644 --- a/layout/reftests/canvas/text-horzline-with-bottom.html +++ b/layout/reftests/canvas/text-horzline-with-bottom.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); diff --git a/layout/reftests/canvas/text-horzline-with-top.html b/layout/reftests/canvas/text-horzline-with-top.html index a47e5c4e3350..d9d393c06746 100644 --- a/layout/reftests/canvas/text-horzline-with-top.html +++ b/layout/reftests/canvas/text-horzline-with-top.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 = 'top'; ctx.fillText('TEXT', 64, 32);