From f3f684e136cd796f307b787f62a7d652c5e16cf1 Mon Sep 17 00:00:00 2001 From: "roc+@cs.cmu.edu" Date: Mon, 17 Mar 2008 18:45:05 -0700 Subject: [PATCH] Bug 392233. Take the current contxt scale into account when constructing textruns for SVG, and simplify the SVG text code. r=longsonr,sr=vlad --- content/svg/content/test/Makefile.in | 2 + content/svg/content/test/test_text.html | 180 +++ content/svg/content/test/text-helper.svg | 17 + layout/reftests/svg/reftest.list | 1 + layout/reftests/svg/text-scale-01-ref.svg | 14 + layout/reftests/svg/text-scale-01.svg | 20 + layout/svg/base/src/nsISVGGlyphFragmentLeaf.h | 11 +- layout/svg/base/src/nsSVGGlyphFrame.cpp | 1011 +++++++++-------- layout/svg/base/src/nsSVGGlyphFrame.h | 114 +- layout/svg/base/src/nsSVGTextFrame.cpp | 62 +- layout/svg/base/src/nsSVGTextFrame.h | 16 +- 11 files changed, 863 insertions(+), 585 deletions(-) create mode 100644 content/svg/content/test/test_text.html create mode 100644 content/svg/content/test/text-helper.svg create mode 100644 layout/reftests/svg/text-scale-01-ref.svg create mode 100644 layout/reftests/svg/text-scale-01.svg diff --git a/content/svg/content/test/Makefile.in b/content/svg/content/test/Makefile.in index 181b3a1b639f..2e991ec08ff9 100644 --- a/content/svg/content/test/Makefile.in +++ b/content/svg/content/test/Makefile.in @@ -47,6 +47,8 @@ include $(topsrcdir)/config/rules.mk _TEST_FILES = \ test_getSubStringLength.xhtml \ getSubStringLength-helper.svg \ + test_text.html \ + text-helper.svg \ $(NULL) libs:: $(_TEST_FILES) diff --git a/content/svg/content/test/test_text.html b/content/svg/content/test/test_text.html new file mode 100644 index 000000000000..f71a49df42db --- /dev/null +++ b/content/svg/content/test/test_text.html @@ -0,0 +1,180 @@ + + + + + Test for Bug 392233 + + + + + +Mozilla Bug 392233 +

+ + + + +
+
+
+ + diff --git a/content/svg/content/test/text-helper.svg b/content/svg/content/test/text-helper.svg new file mode 100644 index 000000000000..e77431bed0ce --- /dev/null +++ b/content/svg/content/test/text-helper.svg @@ -0,0 +1,17 @@ + + + + + + abc + + + abc + + + abcdefghijklmnopqrstuvwxyz + + diff --git a/layout/reftests/svg/reftest.list b/layout/reftests/svg/reftest.list index 2022e3e11310..8cff25eded0d 100755 --- a/layout/reftests/svg/reftest.list +++ b/layout/reftests/svg/reftest.list @@ -56,6 +56,7 @@ fails == style-property-not-on-script-element-01.svg pass.svg == svg-in-foreignObject-02.xhtml svg-in-foreignObject-01-ref.xhtml # reuse -01-ref.xhtml random-if(MOZ_WIDGET_TOOLKIT=="gtk2") == text-font-weight-01.svg text-font-weight-01-ref.svg # bug 386713 == text-in-link-01.svg text-in-link-01-ref.svg +== text-scale-01.svg text-scale-01-ref.svg == text-style-01a.svg text-style-01-ref.svg == text-style-01b.svg text-style-01-ref.svg == text-style-01c.svg text-style-01-ref.svg diff --git a/layout/reftests/svg/text-scale-01-ref.svg b/layout/reftests/svg/text-scale-01-ref.svg new file mode 100644 index 000000000000..c32890d4f9e3 --- /dev/null +++ b/layout/reftests/svg/text-scale-01-ref.svg @@ -0,0 +1,14 @@ + + + + Reference for text scaling + + + + Hello + Hello + Hello + diff --git a/layout/reftests/svg/text-scale-01.svg b/layout/reftests/svg/text-scale-01.svg new file mode 100644 index 000000000000..9ec5b64e7e66 --- /dev/null +++ b/layout/reftests/svg/text-scale-01.svg @@ -0,0 +1,20 @@ + + + + Testcase for text scaling + + + + + Hello + + + Hello + + + Hello + + diff --git a/layout/svg/base/src/nsISVGGlyphFragmentLeaf.h b/layout/svg/base/src/nsISVGGlyphFragmentLeaf.h index 92ded63b89ef..8c0da4fafbb0 100644 --- a/layout/svg/base/src/nsISVGGlyphFragmentLeaf.h +++ b/layout/svg/base/src/nsISVGGlyphFragmentLeaf.h @@ -46,9 +46,11 @@ class nsIDOMSVGPoint; class nsIDOMSVGRect; class nsSVGTextPathFrame; -// {2C466AED-CF7B-4479-A807-98151215A645} + +// {ec9a9965-3ff2-4bb5-b0e2-dd8830e9f41a} #define NS_ISVGGLYPHFRAGMENTLEAF_IID \ -{ 0x2c466aed, 0xcf7b, 0x4479, { 0xa8, 0x7, 0x98, 0x15, 0x12, 0x15, 0xa6, 0x45 } } + { 0xec9a9965, 0x3ff2, 0x4bb5, \ + { 0xb0, 0xe2, 0xdd, 0x88, 0x30, 0xe9, 0xf4, 0x1a } } class nsISVGGlyphFragmentLeaf : public nsISVGGlyphFragmentNode { @@ -70,8 +72,9 @@ public: enum { BASELINE_TEXT_BEFORE_EDGE = 6U }; enum { BASELINE_TEXT_AFTER_EDGE = 7U }; - NS_IMETHOD_(float) GetBaselineOffset(PRUint16 baselineIdentifier)=0; - NS_IMETHOD_(float) GetAdvance()=0; + NS_IMETHOD_(float) GetBaselineOffset(PRUint16 baselineIdentifier, + PRBool aForceGlobalTransform)=0; + NS_IMETHOD_(float) GetAdvance(PRBool aForceGlobalTransform)=0; NS_IMETHOD_(void) SetGlyphPosition(float x, float y)=0; NS_IMETHOD_(nsSVGTextPathFrame*) FindTextPathParent()=0; diff --git a/layout/svg/base/src/nsSVGGlyphFrame.cpp b/layout/svg/base/src/nsSVGGlyphFrame.cpp index 1177a78eec71..b57b6b729bbd 100644 --- a/layout/svg/base/src/nsSVGGlyphFrame.cpp +++ b/layout/svg/base/src/nsSVGGlyphFrame.cpp @@ -54,12 +54,133 @@ #include "gfxContext.h" #include "gfxMatrix.h" #include "gfxPlatform.h" -#include "gfxTextRunCache.h" +#include "gfxTextRunWordCache.h" -// XXX: This initial straightforward conversion from accessing cairo -// directly to Thebes doesn't handle clusters. Pretty much all code -// that measures or draws single characters (textPath code and some -// DOM accessors) will need to be reworked. +struct CharacterPosition { + gfxPoint pos; + gfxFloat angle; + PRBool draw; +}; + +/** + * This is a do-it-all helper class. It supports iterating through the + * drawable characters of a string. For each character, it can set up + * a graphics context with a transform appropriate for drawing the + * character, or a transform appropriate for emitting geometry in the + * text metrics coordinate system (which differs from the drawing + * coordinate system by a scale factor of AppUnitPerCSSPixels). These + * transforms include offsets and rotations of characters along paths, and + * the mPosition of the nsSVGGlyphFrame. + * + * This helper also creates the textrun as needed. It supports detecting + * the special case when the entire textrun can be drawn or measured + * as a unit, and setting the graphics context transform up for that. It + * takes care of setting up the global transform if requested. It also + * provides direct access to the character path position data for the + * DOM APIs that need that. + * + * If an error occurs, for example, a canvas TM is not available because + * the element is in a section, then the CharacterIterator will + * behave as if the frame has no drawable characters. + * + * XXX should make this iterate clusters instead + * XXX needs RTL love + * XXX might want to make AdvanceToCharacter constant time (e.g. by + * caching advances and/or the CharacterPosition array across DOM + * API calls) to ensure that calling Get*OfChar (etc) for each character + * in the text is O(N) + */ +class CharacterIterator +{ +public: + /** + * Sets up the iterator so that NextChar will return the first drawable + * char. + * @param aForceGlobalTransform passed on to EnsureTextRun (see below) + */ + CharacterIterator(nsSVGGlyphFrame *aSource, PRBool aForceGlobalTransform); + /** + * This matrix will be applied to aContext in the SetupFor methods below, + * before any glyph translation/rotation. + */ + void SetupGlobalTransform(const gfxMatrix& aInitialMatrix); + + /** + * Try to set up aContext so we can draw the whole textrun at once. + * This applies any global transform requested by SetupGlobalTransform, + * then applies the positioning of the text. Returns false if drawing + * the whole textrun at once is impossible due to individual positioning + * and/or rotation of glyphs. + */ + PRBool SetupForDirectTextRunDrawing(gfxContext *aContext) { + return SetupForDirectTextRun(aContext, mDrawScale); + } + /** + * Try to set up aContext so we can measure the whole textrun at once. + * This applies any global transform requested by SetupGlobalTransform, + * then applies the positioning of the text, then applies a scale + * from appunits to device pixels so drawing in appunits works. + * Returns false if drawing the whole textrun at once is impossible due + * to individual positioning and/or rotation of glyphs. + */ + PRBool SetupForDirectTextRunMetrics(gfxContext *aContext) { + return SetupForDirectTextRun(aContext, mMetricsScale); + } + + /** + * Sets up the iterator so that NextChar will return the first drawable + * char. + */ + void Reset() { mCurrentChar = -1; mCurrentAdvance = 0; } + /** + * Returns the index of the next char in the string that should be + * drawn, or -1 if there is no such character. + */ + PRInt32 NextChar(); + /** + * Repeated calls NextChar until it returns aIndex (i.e. aIndex is the + * current drawable character). Returns false if that never happens + * (because aIndex is before or equal to the current character, or + * out of bounds, or not drawable). + */ + PRBool AdvanceToCharacter(PRInt32 aIndex); + + /** + * Set up aContext for glyph drawing. This applies any global transform + * requested by SetupGlobalTransform, then applies any positioning and + * rotation for the current character. + */ + void SetupForDrawing(gfxContext *aContext) { + return SetupFor(aContext, mDrawScale); + } + /** + * Set up aContext for glyph measuring. This applies any global transform + * requested by SetupGlobalTransform, then applies any positioning and + * rotation for the current character, then applies a scale from appunits + * to device pixels so that drawing in appunits sizes works. + */ + void SetupForMetrics(gfxContext *aContext) { + return SetupFor(aContext, mMetricsScale); + } + /** + * Get the raw position data for the current character. + */ + CharacterPosition GetPositionData(); + +private: + PRBool SetupForDirectTextRun(gfxContext *aContext, float aScale); + void SetupFor(gfxContext *aContext, float aScale); + + nsSVGGlyphFrame *mSource; + nsAutoTArray mPositions; + gfxMatrix mInitialMatrix; + // Textrun advance width from start to mCurrentChar, in appunits + gfxFloat mCurrentAdvance; + PRInt32 mCurrentChar; + float mDrawScale; + float mMetricsScale; + PRPackedBool mInError; +}; //---------------------------------------------------------------------- // Implementation @@ -95,6 +216,7 @@ nsSVGGlyphFrame::CharacterDataChanged(nsPresContext* aPresContext, nsIContent* aChild, PRBool aAppend) { + ClearTextRun(); return UpdateGraphic(); } @@ -109,39 +231,16 @@ nsSVGGlyphFrame::UpdateGraphic(PRBool suppressInvalidation) return NS_OK; } +// Usable font size range in devpixels / user-units +#define CLAMP_MIN_SIZE 8 +#define CLAMP_MAX_SIZE 200 +#define PRECISE_SIZE 200 + NS_IMETHODIMP nsSVGGlyphFrame::DidSetStyleContext() { nsSVGGlyphFrameBase::DidSetStyleContext(); - - const nsStyleFont* fontData = GetStyleFont(); - nsFont font = fontData->mFont; - - // Since SVG has its own scaling, we really don't want - // fonts in SVG to respond to the browser's "TextZoom" - // (Ctrl++,Ctrl+-) - nsPresContext *presContext = PresContext(); - float textZoom = presContext->TextZoom(); - double size = - presContext->AppUnitsToFloatCSSPixels(fontData->mSize) / textZoom; - - nsCAutoString langGroup; - nsIAtom *langGroupAtom = presContext->GetLangGroup(); - if (langGroupAtom) { - const char* lg; - langGroupAtom->GetUTF8String(&lg); - langGroup.Assign(lg); - } - - mFontStyle = new gfxFontStyle(font.style, font.weight, size, langGroup, - font.sizeAdjust, font.systemFont, - font.familyNameQuirks); - - if (mFontStyle) { - mFontGroup = - gfxPlatform::GetPlatform()->CreateFontGroup(font.name, mFontStyle); - } - + ClearTextRun(); return UpdateGraphic(); } @@ -203,83 +302,18 @@ nsSVGGlyphFrame::GetType() const //---------------------------------------------------------------------- // nsISVGChildFrame methods -void -nsSVGGlyphFrame::LoopCharacters(gfxContext *aCtx, const nsString &aText, - const nsSVGCharacterPosition *aCP, - FillOrStroke aFillOrStroke) -{ - gfxTextRunCache::AutoTextRun textRun = GetTextRun(aCtx, aText); - - if (!textRun.get()) - return; - - if (!aCP) { - if (aFillOrStroke == STROKE) { - textRun->DrawToPath(aCtx, mPosition, 0, aText.Length(), nsnull, nsnull); - } else { - textRun->Draw(aCtx, mPosition, 0, aText.Length(), - nsnull, nsnull, nsnull); - } - } else { - for (PRUint32 i = 0; i < aText.Length(); i++) { - /* character actually on the path? */ - if (aCP[i].draw == PR_FALSE) - continue; - - gfxMatrix matrix = aCtx->CurrentMatrix(); - - gfxMatrix rot; - rot.Rotate(aCP[i].angle); - aCtx->Multiply(rot); - - rot.Invert(); - gfxPoint pt = rot.Transform(aCP[i].pos); - - if (aFillOrStroke == STROKE) { - textRun->DrawToPath(aCtx, pt, i, 1, nsnull, nsnull); - } else { - textRun->Draw(aCtx, pt, i, 1, nsnull, nsnull, nsnull); - } - aCtx->SetMatrix(matrix); - } - } -} - NS_IMETHODIMP nsSVGGlyphFrame::PaintSVG(nsSVGRenderState *aContext, nsRect *aDirtyRect) { if (!GetStyleVisibility()->IsVisible()) return NS_OK; - nsAutoString text; - if (!GetCharacterData(text)) { - return NS_OK; - } - - nsAutoArrayPtr cp; - gfxContext *gfx = aContext->GetGfxContext(); - - nsresult rv = GetCharacterPosition(gfx, text, getter_Transfers(cp)); - - gfxMatrix matrix; - PRUint16 renderMode = aContext->GetRenderMode(); + gfxMatrix matrix = gfx->CurrentMatrix(); - if (renderMode == nsSVGRenderState::NORMAL) { - /* save/pop the state so we don't screw up the xform */ - gfx->Save(); - } - else { - matrix = gfx->CurrentMatrix(); - } - - rv = GetGlobalTransform(gfx); - if (NS_FAILED(rv)) { - if (renderMode == nsSVGRenderState::NORMAL) - gfx->Restore(); - return rv; - } + CharacterIterator iter(this, PR_TRUE); + iter.SetupGlobalTransform(matrix); if (renderMode != nsSVGRenderState::NORMAL) { if (GetClipRule() == NS_STYLE_FILL_RULE_EVENODD) @@ -290,27 +324,30 @@ nsSVGGlyphFrame::PaintSVG(nsSVGRenderState *aContext, nsRect *aDirtyRect) if (renderMode == nsSVGRenderState::CLIP_MASK) { gfx->SetAntialiasMode(gfxContext::MODE_ALIASED); gfx->SetColor(gfxRGBA(1.0f, 1.0f, 1.0f, 1.0f)); - LoopCharacters(gfx, text, cp, FILL); + FillCharacters(&iter, gfx); } else { - LoopCharacters(gfx, text, cp, STROKE); + AddCharactersToPath(&iter, gfx); } gfx->SetMatrix(matrix); - return NS_OK; } + // XXX why save+restore instead of just saving the matrix? + gfx->Save(); if (HasFill() && SetupCairoFill(gfx)) { - LoopCharacters(gfx, text, cp, FILL); + FillCharacters(&iter, gfx); + iter.Reset(); } if (HasStroke() && SetupCairoStroke(gfx)) { gfx->NewPath(); - LoopCharacters(gfx, text, cp, STROKE); + AddCharactersToPath(&iter, gfx); gfx->Stroke(); + // We need to clear the context's path so state doesn't leak + // out. See bug 337753. gfx->NewPath(); } - gfx->Restore(); return NS_OK; @@ -375,86 +412,33 @@ nsSVGGlyphFrame::GetCoveredRegion() return mRect; } +static gfxContext * +MakeTmpCtx() { + return new gfxContext(nsSVGUtils::GetThebesComputationalSurface()); +} + NS_IMETHODIMP nsSVGGlyphFrame::UpdateCoveredRegion() { - mRect.Empty(); - - PRBool hasFill = HasFill(); - PRBool hasStroke = HasStroke(); - - if (!hasFill && !hasStroke) { - return NS_OK; - } - - nsAutoString text; - if (!GetCharacterData(text)) { - return NS_OK; - } - - nsAutoArrayPtr cp; - - nsSVGAutoGlyphHelperContext ctx(this, text, getter_Transfers(cp)); - - gfxContext *gfx = ctx.GetContext(); - gfxTextRun *textRun = ctx.GetTextRun(); - if (!gfx || !textRun) - return NS_ERROR_OUT_OF_MEMORY; - - nsresult rv = GetGlobalTransform(gfx); - NS_ENSURE_SUCCESS(rv, rv); - - if (!cp) { - if (hasStroke) { - textRun->DrawToPath(gfx, mPosition, 0, text.Length(), nsnull, nsnull); - } else { - gfxTextRun::Metrics metrics = - textRun->MeasureText(0, text.Length(), PR_FALSE, nsnull, nsnull); - gfx->Rectangle(metrics.mBoundingBox + mPosition); - } - } else { - for (PRUint32 i=0; iCurrentMatrix(); - - if (hasStroke) { - gfxMatrix rot; - rot.Rotate(cp[i].angle); - gfx->Multiply(rot); - - rot.Invert(); - gfxPoint pt = rot.Transform(cp[i].pos); - - textRun->DrawToPath(gfx, pt, i, 1, nsnull, nsnull); - } else { - gfx->MoveTo(cp[i].pos); - gfx->Rotate(cp[i].angle); - - gfxTextRun::Metrics metrics = - textRun->MeasureText(i, 1, PR_FALSE, nsnull, nsnull); - - gfx->Rectangle(metrics.mBoundingBox + gfx->CurrentPoint()); - } - gfx->SetMatrix(matrix); - } - } - + nsRefPtr tmpCtx = MakeTmpCtx(); + CharacterIterator iter(this, PR_TRUE); + iter.SetupGlobalTransform(gfxMatrix()); + gfxRect extent; - if (hasStroke) { - SetupCairoStrokeGeometry(gfx); - extent = gfx->GetUserStrokeExtent(); - extent = gfx->UserToDevice(extent); + if (HasStroke()) { + AddCharactersToPath(&iter, tmpCtx); + SetupCairoStrokeGeometry(tmpCtx); + extent = tmpCtx->UserToDevice(tmpCtx->GetUserStrokeExtent()); + } else if (HasFill()) { + AddBoundingBoxesToPath(&iter, tmpCtx); + tmpCtx->IdentityMatrix(); + extent = tmpCtx->GetUserPathExtent(); } else { - gfx->IdentityMatrix(); - extent = gfx->GetUserPathExtent(); + extent = gfxRect(0, 0, 0, 0); } mRect = nsSVGUtils::ToBoundingPixelRect(extent); - return NS_OK; } @@ -478,6 +462,9 @@ nsSVGGlyphFrame::InitialUpdate() void nsSVGGlyphFrame::NotifySVGChanged(PRUint32 aFlags) { + if (aFlags & TRANSFORM_CHANGED) { + ClearTextRun(); + } UpdateGeometry(PR_TRUE, (aFlags & SUPPRESS_INVALIDATION) != 0); } @@ -497,32 +484,73 @@ nsSVGGlyphFrame::NotifyRedrawUnsuspended() return NS_OK; } +void +nsSVGGlyphFrame::AddCharactersToPath(CharacterIterator *aIter, + gfxContext *aContext) +{ + if (aIter->SetupForDirectTextRunDrawing(aContext)) { + mTextRun->DrawToPath(aContext, gfxPoint(0, 0), 0, + mTextRun->GetLength(), nsnull, nsnull); + return; + } + + PRInt32 i; + while ((i = aIter->NextChar()) >= 0) { + aIter->SetupForDrawing(aContext); + mTextRun->DrawToPath(aContext, gfxPoint(0, 0), i, 1, nsnull, nsnull); + } +} + +void +nsSVGGlyphFrame::AddBoundingBoxesToPath(CharacterIterator *aIter, + gfxContext *aContext) +{ + if (aIter->SetupForDirectTextRunMetrics(aContext)) { + gfxTextRun::Metrics metrics = + mTextRun->MeasureText(0, mTextRun->GetLength(), + PR_FALSE, nsnull, nsnull); + aContext->Rectangle(metrics.mBoundingBox); + return; + } + + PRInt32 i; + while ((i = aIter->NextChar()) >= 0) { + aIter->SetupForMetrics(aContext); + gfxTextRun::Metrics metrics = + mTextRun->MeasureText(i, 1, PR_FALSE, nsnull, nsnull); + aContext->Rectangle(metrics.mBoundingBox); + } +} + +void +nsSVGGlyphFrame::FillCharacters(CharacterIterator *aIter, + gfxContext *aContext) +{ + if (aIter->SetupForDirectTextRunDrawing(aContext)) { + mTextRun->Draw(aContext, gfxPoint(0, 0), 0, + mTextRun->GetLength(), nsnull, nsnull, nsnull); + return; + } + + PRInt32 i; + while ((i = aIter->NextChar()) >= 0) { + aIter->SetupForDrawing(aContext); + mTextRun->Draw(aContext, gfxPoint(0, 0), i, 1, nsnull, nsnull, nsnull); + } +} + NS_IMETHODIMP nsSVGGlyphFrame::GetBBox(nsIDOMSVGRect **_retval) { *_retval = nsnull; - nsAutoString text; - if (!GetCharacterData(text)) { - return NS_OK; - } + nsRefPtr tmpCtx = MakeTmpCtx(); + CharacterIterator iter(this, PR_TRUE); + iter.SetupGlobalTransform(gfxMatrix()); + AddCharactersToPath(&iter, tmpCtx); - nsAutoArrayPtr cp; - - nsSVGAutoGlyphHelperContext ctx(this, text, getter_Transfers(cp)); - - gfxContext *gfx = ctx.GetContext(); - if (!gfx) - return NS_ERROR_OUT_OF_MEMORY; - - nsresult rv = GetGlobalTransform(gfx); - NS_ENSURE_SUCCESS(rv, rv); - - LoopCharacters(gfx, text, cp, STROKE); - gfx->IdentityMatrix(); - gfxRect rect = gfx->GetUserPathExtent(); - - return NS_NewSVGRect(_retval, rect); + tmpCtx->IdentityMatrix(); + return NS_NewSVGRect(_retval, tmpCtx->GetUserPathExtent()); } //---------------------------------------------------------------------- @@ -537,9 +565,8 @@ nsSVGGlyphFrame::GetCanvasTM(nsIDOMSVGMatrix * *aCTM) nsSVGContainerFrame *containerFrame = static_cast (mParent); nsCOMPtr parentTM = containerFrame->GetCanvasTM(); - - *aCTM = parentTM.get(); - NS_ADDREF(*aCTM); + *aCTM = nsnull; + parentTM.swap(*aCTM); return NS_OK; } @@ -573,43 +600,40 @@ nsSVGGlyphFrame::GetCharacterData(nsAString & aCharacterData) return !characterData.IsEmpty(); } -nsresult -nsSVGGlyphFrame::GetCharacterPosition(gfxContext *aContext, - const nsString &aText, - nsSVGCharacterPosition **aCharacterPosition) +PRBool +nsSVGGlyphFrame::GetCharacterPositions(nsTArray* aCharacterPositions, + float aMetricsScale) { - *aCharacterPosition = nsnull; - - NS_ASSERTION(!aText.IsEmpty(), "no text"); + NS_ASSERTION(mTextRun->GetLength() > 0, "no text"); nsSVGTextPathFrame *textPath = FindTextPathParent(); /* we're an ordinary fragment - return */ /* XXX: we might want to use this for individual x/y/dx/dy adjustment */ if (!textPath) - return NS_OK; + return PR_TRUE; nsRefPtr data = textPath->GetFlattenedPath(); /* textPath frame, but invalid target */ if (!data) - return NS_ERROR_FAILURE; + return PR_FALSE; gfxFloat length = data->GetLength(); - PRUint32 strLength = aText.Length(); + PRUint32 strLength = mTextRun->GetLength(); - gfxTextRunCache::AutoTextRun textRun = GetTextRun(aContext, aText); - if (!textRun.get()) - return NS_ERROR_OUT_OF_MEMORY; + if (!aCharacterPositions->SetLength(strLength)) + return PR_FALSE; - nsSVGCharacterPosition *cp = new nsSVGCharacterPosition[strLength]; + CharacterPosition *cp = aCharacterPositions->Elements(); for (PRUint32 k = 0; k < strLength; k++) cp[k].draw = PR_FALSE; gfxFloat x = mPosition.x; for (PRUint32 i = 0; i < strLength; i++) { - gfxFloat halfAdvance = textRun->GetAdvanceWidth(i, 1, nsnull) / 2.0; + gfxFloat halfAdvance = + mTextRun->GetAdvanceWidth(i, 1, nsnull)*aMetricsScale / 2.0; /* have we run off the end of the path? */ if (x + halfAdvance > length) @@ -629,10 +653,8 @@ nsSVGGlyphFrame::GetCharacterPosition(gfxContext *aContext, } x += 2 * halfAdvance; } - - *aCharacterPosition = cp; - - return NS_OK; + + return PR_TRUE; } //---------------------------------------------------------------------- @@ -787,70 +809,33 @@ nsSVGGlyphFrame::SetGlyphPosition(float x, float y) } NS_IMETHODIMP -nsSVGGlyphFrame::GetStartPositionOfChar(PRUint32 charnum, nsIDOMSVGPoint **_retval) +nsSVGGlyphFrame::GetStartPositionOfChar(PRUint32 charnum, + nsIDOMSVGPoint **_retval) { *_retval = nsnull; - nsAutoString text; - GetCharacterData(text); + CharacterIterator iter(this, PR_FALSE); + if (!iter.AdvanceToCharacter(charnum)) + return NS_ERROR_DOM_INDEX_SIZE_ERR; - nsAutoArrayPtr cp; - - nsSVGAutoGlyphHelperContext ctx(this, text, getter_Transfers(cp)); - - gfxPoint pt; - - if (cp) { - if (cp[charnum].draw == PR_FALSE) { - return NS_ERROR_DOM_INDEX_SIZE_ERR; - } - - pt = cp[charnum].pos; - } else { - pt = mPosition; - - if (charnum > 0) { - gfxTextRun *textRun = ctx.GetTextRun(); - if (!textRun) - return NS_ERROR_OUT_OF_MEMORY; - pt.x += textRun->GetAdvanceWidth(0, charnum, nsnull); - } - } - - return NS_NewSVGPoint(_retval, pt); + return NS_NewSVGPoint(_retval, iter.GetPositionData().pos); } NS_IMETHODIMP -nsSVGGlyphFrame::GetEndPositionOfChar(PRUint32 charnum, nsIDOMSVGPoint **_retval) +nsSVGGlyphFrame::GetEndPositionOfChar(PRUint32 charnum, + nsIDOMSVGPoint **_retval) { *_retval = nsnull; - nsAutoString text; - GetCharacterData(text); + CharacterIterator iter(this, PR_FALSE); + if (!iter.AdvanceToCharacter(charnum)) + return NS_ERROR_DOM_INDEX_SIZE_ERR; - nsAutoArrayPtr cp; - - nsSVGAutoGlyphHelperContext ctx(this, text, getter_Transfers(cp)); - gfxTextRun *textRun = ctx.GetTextRun(); - if (!textRun) - return NS_ERROR_OUT_OF_MEMORY; - - gfxPoint pt; - - if (cp) { - if (cp[charnum].draw == PR_FALSE) { - return NS_ERROR_DOM_INDEX_SIZE_ERR; - } - - gfxFloat advance = textRun->GetAdvanceWidth(charnum, 1, nsnull); - pt = cp[charnum].pos + - gfxPoint(cos(cp[charnum].angle), sin(cp[charnum].angle)) * advance; - } else { - pt = mPosition; - pt.x += textRun->GetAdvanceWidth(0, charnum + 1, nsnull); - } - - return NS_NewSVGPoint(_retval, pt); + nsRefPtr tmpCtx = MakeTmpCtx(); + iter.SetupForMetrics(tmpCtx); + tmpCtx->MoveTo(gfxPoint(mTextRun->GetAdvanceWidth(charnum, 1, nsnull), 0)); + tmpCtx->IdentityMatrix(); + return NS_NewSVGPoint(_retval, tmpCtx->CurrentPoint()); } NS_IMETHODIMP @@ -858,132 +843,82 @@ nsSVGGlyphFrame::GetExtentOfChar(PRUint32 charnum, nsIDOMSVGRect **_retval) { *_retval = nsnull; - nsAutoString text; - GetCharacterData(text); - - nsAutoArrayPtr cp; - - nsSVGAutoGlyphHelperContext ctx(this, text, getter_Transfers(cp)); - gfxTextRun *textRun = ctx.GetTextRun(); - if (!textRun) - return NS_ERROR_OUT_OF_MEMORY; + CharacterIterator iter(this, PR_FALSE); + if (!iter.AdvanceToCharacter(charnum)) + return NS_ERROR_DOM_INDEX_SIZE_ERR; gfxTextRun::Metrics metrics = - textRun->MeasureText(charnum, 1, PR_FALSE, nsnull, nsnull); + mTextRun->MeasureText(charnum, 1, PR_FALSE, nsnull, nsnull); - if (cp) { - if (cp[charnum].draw == PR_FALSE) { - return NS_ERROR_DOM_INDEX_SIZE_ERR; - } - - gfxContext *gfx = ctx.GetContext(); - if (!gfx) - return NS_ERROR_OUT_OF_MEMORY; - - gfxMatrix matrix = gfx->CurrentMatrix(); - - gfx->MoveTo(cp[charnum].pos); - gfx->Rotate(cp[charnum].angle); - - gfx->Rectangle(metrics.mBoundingBox + gfx->CurrentPoint()); - - gfx->IdentityMatrix(); - - gfxRect rect = gfx->GetUserPathExtent(); - - gfx->SetMatrix(matrix); - - return NS_NewSVGRect(_retval, rect); - } - - gfxPoint pt = mPosition; - - if (charnum > 0) { - // add the space taken up by the text which comes before charnum - // to the position of the charnum character - pt.x += textRun->GetAdvanceWidth(0, charnum, nsnull); - } - - return NS_NewSVGRect(_retval, metrics.mBoundingBox + pt); + nsRefPtr tmpCtx = MakeTmpCtx(); + iter.SetupForMetrics(tmpCtx); + tmpCtx->Rectangle(metrics.mBoundingBox); + tmpCtx->IdentityMatrix(); + return NS_NewSVGRect(_retval, tmpCtx->GetUserPathExtent()); } NS_IMETHODIMP nsSVGGlyphFrame::GetRotationOfChar(PRUint32 charnum, float *_retval) { + CharacterIterator iter(this, PR_FALSE); + if (!iter.AdvanceToCharacter(charnum)) + return NS_ERROR_DOM_INDEX_SIZE_ERR; + + CharacterPosition pos = iter.GetPositionData(); + if (!pos.draw) + return NS_ERROR_DOM_INDEX_SIZE_ERR; + const gfxFloat radPerDeg = M_PI/180.0; - - nsAutoString text; - GetCharacterData(text); - - nsAutoArrayPtr cp; - - nsSVGAutoGlyphHelperContext ctx(this, text, getter_Transfers(cp)); - - if (cp) { - if (cp[charnum].draw == PR_FALSE) { - return NS_ERROR_DOM_INDEX_SIZE_ERR; - } - - *_retval = float(cp[charnum].angle / radPerDeg); - } else { - *_retval = 0.0f; - } + *_retval = float(pos.angle / radPerDeg); return NS_OK; } NS_IMETHODIMP_(float) -nsSVGGlyphFrame::GetBaselineOffset(PRUint16 baselineIdentifier) +nsSVGGlyphFrame::GetBaselineOffset(PRUint16 baselineIdentifier, + PRBool aForceGlobalTransform) { - float _retval; + float drawScale, metricsScale; - nsAutoString text; - GetCharacterData(text); - - nsSVGAutoGlyphHelperContext ctx(this, text); - gfxTextRun *textRun = ctx.GetTextRun(); - if (!textRun) + if (!EnsureTextRun(&drawScale, &metricsScale, aForceGlobalTransform)) return 0.0f; gfxTextRun::Metrics metrics = - textRun->MeasureText(0, text.Length(), PR_FALSE, nsnull, nsnull); + mTextRun->MeasureText(0, mTextRun->GetLength(), + PR_FALSE, nsnull, nsnull); + gfxFloat baselineAppUnits; switch (baselineIdentifier) { case BASELINE_HANGING: // not really right, but the best we can do with the information provided // FALLTHROUGH case BASELINE_TEXT_BEFORE_EDGE: - _retval = -float(metrics.mAscent); + baselineAppUnits = -metrics.mAscent; break; case BASELINE_TEXT_AFTER_EDGE: - _retval = float(metrics.mDescent); + baselineAppUnits = metrics.mDescent; break; case BASELINE_CENTRAL: case BASELINE_MIDDLE: - _retval = -float(metrics.mAscent - metrics.mDescent) / 2.0f; + baselineAppUnits = -(metrics.mAscent - metrics.mDescent) / 2.0f; break; case BASELINE_ALPHABETIC: default: - _retval = 0.0f; + baselineAppUnits = 0.0; break; } - - return _retval; + return float(baselineAppUnits)*metricsScale; } NS_IMETHODIMP_(float) -nsSVGGlyphFrame::GetAdvance() +nsSVGGlyphFrame::GetAdvance(PRBool aForceGlobalTransform) { - nsAutoString text; - if (!GetCharacterData(text)) { - return 0.0f; - } - - nsSVGAutoGlyphHelperContext ctx(this, text); - gfxTextRun *textRun = ctx.GetTextRun(); - if (!textRun) + float drawScale, metricsScale; + if (!EnsureTextRun(&drawScale, &metricsScale, aForceGlobalTransform)) return 0.0f; - return float(textRun->GetAdvanceWidth(0, text.Length(), nsnull)); + gfxFloat advanceAppUnits = + mTextRun->GetAdvanceWidth(0, mTextRun->GetLength(), nsnull); + return float(advanceAppUnits)*metricsScale; } NS_IMETHODIMP_(nsSVGTextPathFrame*) @@ -1111,21 +1046,19 @@ nsSVGGlyphFrame::GetNumberOfChars() NS_IMETHODIMP_(float) nsSVGGlyphFrame::GetComputedTextLength() { - return GetAdvance(); + return GetAdvance(PR_FALSE); } NS_IMETHODIMP_(float) nsSVGGlyphFrame::GetSubStringLength(PRUint32 charnum, PRUint32 fragmentChars) { - nsAutoString text; - GetCharacterData(text); - - nsSVGAutoGlyphHelperContext ctx(this, text); - gfxTextRun *textRun = ctx.GetTextRun(); - if (!textRun) + float drawScale, metricsScale; + if (!EnsureTextRun(&drawScale, &metricsScale, PR_FALSE)) return 0.0f; - return float(textRun->GetAdvanceWidth(charnum, fragmentChars, nsnull)); + gfxFloat advanceAppUnits = + mTextRun->GetAdvanceWidth(charnum, fragmentChars, nsnull); + return float(advanceAppUnits)*metricsScale; } NS_IMETHODIMP_(PRInt32) @@ -1135,57 +1068,27 @@ nsSVGGlyphFrame::GetCharNumAtPosition(nsIDOMSVGPoint *point) point->GetX(&xPos); point->GetY(&yPos); - nsAutoString text; - GetCharacterData(text); - - nsAutoArrayPtr cp; - - nsSVGAutoGlyphHelperContext ctx(this, text, getter_Transfers(cp)); - - gfxPoint pt; - if (!cp) { - pt = mPosition; - } - - gfxContext *gfx = ctx.GetContext(); - gfxTextRun *textRun = ctx.GetTextRun(); - if (!gfx || !textRun) - return NS_ERROR_OUT_OF_MEMORY; - - for (PRUint32 charnum = 0; charnum < text.Length(); charnum++) { - /* character actually on the path? */ - if (cp && cp[charnum].draw == PR_FALSE) - continue; - - gfxMatrix matrix = gfx->CurrentMatrix(); - gfx->NewPath(); - - if (cp) { - gfx->MoveTo(cp[charnum].pos); - gfx->Rotate(cp[charnum].angle); - } else { - if (charnum > 0) { - gfx->MoveTo(pt + gfxPoint(textRun->GetAdvanceWidth(0, charnum, nsnull), - 0)); - } else { - gfx->MoveTo(pt); - } - } + nsRefPtr tmpCtx = MakeTmpCtx(); + CharacterIterator iter(this, PR_FALSE); + PRInt32 i; + PRInt32 last = -1; + gfxPoint pt(xPos, yPos); + while ((i = iter.NextChar()) >= 0) { gfxTextRun::Metrics metrics = - textRun->MeasureText(charnum, 1, PR_FALSE, nsnull, nsnull); - - gfx->Rectangle(metrics.mBoundingBox + gfx->CurrentPoint()); - - gfx->IdentityMatrix(); - if (gfx->PointInFill(gfxPoint(xPos, yPos))) { - gfx->SetMatrix(matrix); - return charnum; + mTextRun->MeasureText(i, 1, PR_FALSE, nsnull, nsnull); + iter.SetupForMetrics(tmpCtx); + tmpCtx->NewPath(); + tmpCtx->Rectangle(metrics.mBoundingBox); + tmpCtx->IdentityMatrix(); + if (tmpCtx->PointInFill(pt)) { + // Can't return now. If there's glyph overlap, the last character + // to be rendered wins. + last = i; } - - gfx->SetMatrix(matrix); } - return -1; + + return last; } NS_IMETHODIMP_(nsISVGGlyphFragmentLeaf *) @@ -1259,115 +1162,231 @@ void nsSVGGlyphFrame::UpdateGeometry(PRBool bRedraw, PRBool nsSVGGlyphFrame::ContainsPoint(float x, float y) { - nsAutoString text; - if (!GetCharacterData(text)) { - return PR_FALSE; - } - - nsAutoArrayPtr cp; - - nsSVGAutoGlyphHelperContext ctx(this, text, getter_Transfers(cp)); - gfxContext *gfx = ctx.GetContext(); - gfxTextRun *textRun = ctx.GetTextRun(); - if (!gfx || !textRun) - return PR_FALSE; - - nsresult rv = GetGlobalTransform(gfx); - NS_ENSURE_SUCCESS(rv, PR_FALSE); - - gfxPoint pt; - if (!cp) { - pt = mPosition; - } - - for (PRUint32 i = 0; i < text.Length(); i++) { - /* character actually on the path? */ - if (cp && cp[i].draw == PR_FALSE) - continue; - - gfxMatrix matrix = gfx->CurrentMatrix(); - - if (cp) { - gfx->MoveTo(cp[i].pos); - gfx->Rotate(cp[i].angle); - } else { - gfx->MoveTo(pt); - } - + nsRefPtr tmpCtx = MakeTmpCtx(); + CharacterIterator iter(this, PR_TRUE); + iter.SetupGlobalTransform(gfxMatrix()); + + PRInt32 i; + while ((i = iter.NextChar()) >= 0) { gfxTextRun::Metrics metrics = - textRun->MeasureText(i, 1, PR_FALSE, nsnull, nsnull); - - gfx->Rectangle(metrics.mBoundingBox + gfx->CurrentPoint()); - - gfx->SetMatrix(matrix); - - if (!cp) { - pt.x += metrics.mAdvanceWidth; - } + mTextRun->MeasureText(i, 1, PR_FALSE, nsnull, nsnull); + iter.SetupForMetrics(tmpCtx); + tmpCtx->Rectangle(metrics.mBoundingBox); } - gfx->IdentityMatrix(); - return gfx->PointInFill(gfxPoint(x, y)); + tmpCtx->IdentityMatrix(); + return tmpCtx->PointInFill(gfxPoint(x, y)); } -nsresult -nsSVGGlyphFrame::GetGlobalTransform(gfxContext *aContext) +PRBool +nsSVGGlyphFrame::GetGlobalTransform(gfxMatrix *aMatrix) { nsCOMPtr ctm; GetCanvasTM(getter_AddRefs(ctm)); - NS_ASSERTION(ctm, "graphic source didn't specify a ctm"); + if (!ctm) + return PR_FALSE; gfxMatrix matrix = nsSVGUtils::ConvertSVGMatrixToThebes(ctm); if (matrix.IsSingular()) { - aContext->IdentityMatrix(); - return NS_ERROR_FAILURE; + aMatrix->Reset(); + return PR_FALSE; } - aContext->Multiply(matrix); - - return NS_OK; + *aMatrix = matrix; + return PR_TRUE; } -gfxTextRun * -nsSVGGlyphFrame::GetTextRun(gfxContext *aCtx, const nsString &aText) +void +nsSVGGlyphFrame::ClearTextRun() { - // XXX: should really pass in GetPresContext()->AppUnitsPerDevPixel() - // instead of "1" and do the appropriate unit conversions when sending - // coordinates into thebes and pulling metrics out. - // - // References: - // https://bugzilla.mozilla.org/show_bug.cgi?id=375141 - // http://weblogs.mozillazine.org/roc/archives/2007/03/text_text_text.html - if (!mFontGroup) - return nsnull; + if (!mTextRun) + return; + gfxTextRunWordCache::RemoveTextRun(mTextRun); + delete mTextRun; + mTextRun = nsnull; +} - PRUint32 flags = gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX | +PRBool +nsSVGGlyphFrame::EnsureTextRun(float *aDrawScale, float *aMetricsScale, + PRBool aForceGlobalTransform) +{ + // Compute the size at which the text should render (excluding the CTM) + const nsStyleFont* fontData = GetStyleFont(); + // Since SVG has its own scaling, we really don't want + // fonts in SVG to respond to the browser's "TextZoom" + // (Ctrl++,Ctrl+-) + nsPresContext *presContext = PresContext(); + float textZoom = presContext->TextZoom(); + double size = + presContext->AppUnitsToFloatCSSPixels(fontData->mSize) / textZoom; + + double textRunSize; + if (mTextRun) { + textRunSize = mTextRun->GetFontGroup()->GetStyle()->size; + } else { + nsAutoString text; + if (!GetCharacterData(text)) + return PR_FALSE; + + gfxMatrix m; + if (aForceGlobalTransform || + !(GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD)) { + if (!GetGlobalTransform(&m)) + return PR_FALSE; + } + + // The context scale is the ratio of the length of the transformed + // diagonal vector (1,1) to the length of the untransformed diagonal + // (which is sqrt(2)). + gfxPoint p = m.Transform(gfxPoint(1, 1)) - m.Transform(gfxPoint(0, 0)); + double contextScale = sqrt((p.x*p.x + p.y*p.y)/2); + + nsCAutoString langGroup; + nsIAtom *langGroupAtom = presContext->GetLangGroup(); + if (langGroupAtom) { + const char* lg; + langGroupAtom->GetUTF8String(&lg); + langGroup.Assign(lg); + } + + if (GetStyleSVG()->mTextRendering == + NS_STYLE_TEXT_RENDERING_GEOMETRICPRECISION) { + textRunSize = PRECISE_SIZE; + } else { + textRunSize = size*contextScale; + textRunSize = PR_MAX(textRunSize, CLAMP_MIN_SIZE); + textRunSize = PR_MIN(textRunSize, CLAMP_MAX_SIZE); + } + + const nsFont& font = fontData->mFont; + gfxFontStyle fontStyle(font.style, font.weight, textRunSize, langGroup, + font.sizeAdjust, font.systemFont, + font.familyNameQuirks); + + nsRefPtr fontGroup = + gfxPlatform::GetPlatform()->CreateFontGroup(font.name, &fontStyle); + + PRUint32 flags = gfxTextRunFactory::TEXT_NEED_BOUNDING_BOX | nsLayoutUtils::GetTextRunFlagsForStyle(GetStyleContext(), GetStyleText(), GetStyleFont()); - return gfxTextRunCache::MakeTextRun(aText.get(), aText.Length(), - mFontGroup, aCtx, 1, flags); + + // XXX We should use a better surface here! But then we'd have to + // change things so we can ensure we always have the "right" sort of + // surface available, by creating the textrun only at the right times + nsRefPtr tmpCtx = MakeTmpCtx(); + tmpCtx->SetMatrix(m); + + // Use only the word cache here. We don't want to cache the textrun + // globally because we'll never hit in that cache, since we create + // a new fontgroup every time. Even if we cached fontgroups, we + // might render at very many different sizes (e.g. during zoom + // animation) and caching a textrun for each such size would be bad. + gfxTextRunFactory::Parameters params = { + tmpCtx, nsnull, nsnull, nsnull, 0, GetTextRunUnitsFactor() + }; + mTextRun = gfxTextRunWordCache::MakeTextRun(text.get(), text.Length(), + fontGroup, ¶ms, flags); + if (!mTextRun) + return PR_FALSE; + } + + *aDrawScale = float(size/textRunSize); + *aMetricsScale = (*aDrawScale)/GetTextRunUnitsFactor(); + return PR_TRUE; } //---------------------------------------------------------------------- // helper class -nsSVGGlyphFrame::nsSVGAutoGlyphHelperContext::nsSVGAutoGlyphHelperContext( - nsSVGGlyphFrame *aSource, - const nsString &aText, - nsSVGCharacterPosition **cp) +CharacterIterator::CharacterIterator(nsSVGGlyphFrame *aSource, + PRBool aForceGlobalTransform) + : mSource(aSource), mCurrentAdvance(0), mCurrentChar(-1), + mInError(PR_FALSE) { - Init(aSource, aText); - - nsresult rv = aSource->GetCharacterPosition(mCT, aText, cp); - if (NS_FAILED(rv)) { - NS_WARNING("failed to get character position data"); + if (!aSource->EnsureTextRun(&mDrawScale, &mMetricsScale, + aForceGlobalTransform) || + !aSource->GetCharacterPositions(&mPositions, mMetricsScale)) { + mInError = PR_TRUE; } } void -nsSVGGlyphFrame::nsSVGAutoGlyphHelperContext::Init(nsSVGGlyphFrame *aSource, - const nsString &aText) +CharacterIterator::SetupGlobalTransform(const gfxMatrix& aMatrix) { - mCT = new gfxContext(nsSVGUtils::GetThebesComputationalSurface()); - mTextRun = aSource->GetTextRun(mCT, aText); + if (!mSource->GetGlobalTransform(&mInitialMatrix)) { + mInError = PR_TRUE; + return; + } + mInitialMatrix.Multiply(aMatrix); +} + +PRBool +CharacterIterator::SetupForDirectTextRun(gfxContext *aContext, float aScale) +{ + if (!mPositions.IsEmpty() || mInError) + return PR_FALSE; + aContext->SetMatrix(mInitialMatrix); + aContext->Translate(mSource->mPosition); + aContext->Scale(aScale, aScale); + return PR_TRUE; +} + +PRInt32 +CharacterIterator::NextChar() +{ + if (mInError) + return -1; + + while (PR_TRUE) { + if (mCurrentChar >= 0 && + (mPositions.IsEmpty() || mPositions[mCurrentChar].draw)) { + mCurrentAdvance += + mSource->mTextRun->GetAdvanceWidth(mCurrentChar, 1, nsnull); + } + ++mCurrentChar; + + if (mCurrentChar >= PRInt32(mSource->mTextRun->GetLength())) + return -1; + + if (mPositions.IsEmpty() || mPositions[mCurrentChar].draw) + return mCurrentChar; + } +} + +PRBool +CharacterIterator::AdvanceToCharacter(PRInt32 aIndex) +{ + while (NextChar() != -1) { + if (mCurrentChar == aIndex) + return PR_TRUE; + } + return PR_FALSE; +} + +void +CharacterIterator::SetupFor(gfxContext *aContext, float aScale) +{ + NS_ASSERTION(!mInError, "We should not have reached here"); + + aContext->SetMatrix(mInitialMatrix); + if (mPositions.IsEmpty()) { + gfxFloat advance = mCurrentAdvance/nsSVGGlyphFrame::GetTextRunUnitsFactor(); + aContext->Translate(mSource->mPosition + gfxPoint(advance, 0)); + } else { + aContext->Translate(mPositions[mCurrentChar].pos); + aContext->Rotate(mPositions[mCurrentChar].angle); + } + aContext->Scale(aScale, aScale); +} + +CharacterPosition +CharacterIterator::GetPositionData() +{ + if (!mPositions.IsEmpty()) + return mPositions[mCurrentChar]; + + gfxFloat advance = mCurrentAdvance/nsSVGGlyphFrame::GetTextRunUnitsFactor(); + CharacterPosition cp = + { mSource->mPosition + gfxPoint(advance, 0), 0, PR_TRUE }; + return cp; } diff --git a/layout/svg/base/src/nsSVGGlyphFrame.h b/layout/svg/base/src/nsSVGGlyphFrame.h index 48d259177334..59be72bf7088 100644 --- a/layout/svg/base/src/nsSVGGlyphFrame.h +++ b/layout/svg/base/src/nsSVGGlyphFrame.h @@ -44,11 +44,11 @@ #include "nsISVGChildFrame.h" #include "gfxContext.h" #include "gfxFont.h" -#include "gfxTextRunCache.h" -struct nsSVGCharacterPosition; class nsSVGTextFrame; class nsSVGGlyphFrame; +class CharacterIterator; +struct CharacterPosition; typedef nsSVGGeometryFrame nsSVGGlyphFrameBase; @@ -61,8 +61,14 @@ class nsSVGGlyphFrame : public nsSVGGlyphFrameBase, nsIFrame* parentFrame, nsStyleContext* aContext); protected: nsSVGGlyphFrame(nsStyleContext* aContext) - : nsSVGGlyphFrameBase(aContext), - mWhitespaceHandling(COMPRESS_WHITESPACE) {} + : nsSVGGlyphFrameBase(aContext), + mTextRun(nsnull), + mWhitespaceHandling(COMPRESS_WHITESPACE) + {} + ~nsSVGGlyphFrame() + { + ClearTextRun(); + } public: // nsISupports interface: @@ -107,10 +113,13 @@ public: #endif // nsISVGChildFrame interface: + // These four always use the global transform, even if NS_STATE_NONDISPLAY_CHILD NS_IMETHOD PaintSVG(nsSVGRenderState *aContext, nsRect *aDirtyRect); NS_IMETHOD GetFrameForPointSVG(float x, float y, nsIFrame** hit); - NS_IMETHOD_(nsRect) GetCoveredRegion(); NS_IMETHOD UpdateCoveredRegion(); + NS_IMETHOD GetBBox(nsIDOMSVGRect **_retval); + + NS_IMETHOD_(nsRect) GetCoveredRegion(); NS_IMETHOD InitialUpdate(); virtual void NotifySVGChanged(PRUint32 aFlags); NS_IMETHOD NotifyRedrawSuspended(); @@ -118,7 +127,6 @@ public: NS_IMETHOD SetMatrixPropagation(PRBool aPropagate) { return NS_OK; } NS_IMETHOD SetOverrideCTM(nsIDOMSVGMatrix *aCTM) { return NS_ERROR_FAILURE; } virtual already_AddRefed GetOverrideCTM() { return nsnull; } - NS_IMETHOD GetBBox(nsIDOMSVGRect **_retval); NS_IMETHOD_(PRBool) IsDisplayContainer() { return PR_FALSE; } NS_IMETHOD_(PRBool) HasValidCoveredRect() { return PR_TRUE; } @@ -127,12 +135,22 @@ public: virtual nsresult UpdateGraphic(PRBool suppressInvalidation = PR_FALSE); // nsISVGGlyphFragmentLeaf interface: + // These do not use the global transform if NS_STATE_NONDISPLAY_CHILD NS_IMETHOD GetStartPositionOfChar(PRUint32 charnum, nsIDOMSVGPoint **_retval); NS_IMETHOD GetEndPositionOfChar(PRUint32 charnum, nsIDOMSVGPoint **_retval); NS_IMETHOD GetExtentOfChar(PRUint32 charnum, nsIDOMSVGRect **_retval); NS_IMETHOD GetRotationOfChar(PRUint32 charnum, float *_retval); - NS_IMETHOD_(float) GetBaselineOffset(PRUint16 baselineIdentifier); - NS_IMETHOD_(float) GetAdvance(); + /** + * @param aForceGlobalTransform controls whether to use the + * global transform even when NS_STATE_NONDISPLAY_CHILD + */ + NS_IMETHOD_(float) GetBaselineOffset(PRUint16 baselineIdentifier, + PRBool aForceGlobalTransform); + /** + * @param aForceGlobalTransform controls whether to use the + * global transform even when NS_STATE_NONDISPLAY_CHILD + */ + NS_IMETHOD_(float) GetAdvance(PRBool aForceGlobalTransform); NS_IMETHOD_(void) SetGlyphPosition(float x, float y); NS_IMETHOD_(nsSVGTextPathFrame*) FindTextPathParent(); @@ -147,6 +165,7 @@ public: NS_IMETHOD_(PRBool) IsAbsolutelyPositioned(); // nsISVGGlyphFragmentNode interface: + // These do not use the global transform if NS_STATE_NONDISPLAY_CHILD NS_IMETHOD_(PRUint32) GetNumberOfChars(); NS_IMETHOD_(float) GetComputedTextLength(); NS_IMETHOD_(float) GetSubStringLength(PRUint32 charnum, PRUint32 fragmentChars); @@ -156,66 +175,47 @@ public: NS_IMETHOD_(void) SetWhitespaceHandling(PRUint8 aWhitespaceHandling); protected: - struct nsSVGCharacterPosition { - gfxPoint pos; - gfxFloat angle; - PRBool draw; - }; + friend class CharacterIterator; - // VC6 does not allow the inner class to access protected members - // of the outer class - class nsSVGAutoGlyphHelperContext; - friend class nsSVGAutoGlyphHelperContext; - - // A helper class to deal with gfxTextRuns and temporary thebes - // contexts. - class nsSVGAutoGlyphHelperContext - { - public: - nsSVGAutoGlyphHelperContext(nsSVGGlyphFrame *aSource, - const nsString &aText) - { - Init(aSource, aText); - } - - nsSVGAutoGlyphHelperContext(nsSVGGlyphFrame *aSource, - const nsString &aText, - nsSVGCharacterPosition **cp); - - gfxContext *GetContext() { return mCT; } - gfxTextRun *GetTextRun() { return mTextRun.get(); } - - private: - void Init(nsSVGGlyphFrame *aSource, const nsString &aText); - - nsRefPtr mCT; - gfxTextRunCache::AutoTextRun mTextRun; - }; - - // The textrun must be released via gfxTextRunCache::AutoTextRun - gfxTextRun *GetTextRun(gfxContext *aCtx, - const nsString &aText); + // Use a power of 2 here. It's not so important to match + // nsIDeviceContext::AppUnitsPerDevPixel, but since we do a lot of + // multiplying by 1/GetTextRunUnitsFactor, it's good for it to be a + // power of 2 to avoid accuracy loss. + static PRUint32 GetTextRunUnitsFactor() { return 64; } + + /** + * @aParam aDrawScale font drawing must be scaled into user units + * by this factor + * @param aMetricsScale font metrics must be scaled into user units + * by this factor + * @param aForceGlobalTransform set to true if we should force use of + * the global transform; otherwise we won't use the global transform + * if we're a NONDISPLAY_CHILD + */ + PRBool EnsureTextRun(float *aDrawScale, float *aMetricsScale, + PRBool aForceGlobalTransform); + void ClearTextRun(); PRBool GetCharacterData(nsAString & aCharacterData); - nsresult GetCharacterPosition(gfxContext *aContext, - const nsString &aText, - nsSVGCharacterPosition **aCharacterPosition); + PRBool GetCharacterPositions(nsTArray* aCharacterPositions, + float aMetricsScale); - enum FillOrStroke { FILL, STROKE}; - - void LoopCharacters(gfxContext *aCtx, const nsString &aText, - const nsSVGCharacterPosition *aCP, - FillOrStroke aFillOrStroke); + void AddCharactersToPath(CharacterIterator *aIter, + gfxContext *aContext); + void AddBoundingBoxesToPath(CharacterIterator *aIter, + gfxContext *aContext); + void FillCharacters(CharacterIterator *aIter, + gfxContext *aContext); void UpdateGeometry(PRBool bRedraw, PRBool suppressInvalidation); void UpdateMetrics(); PRBool ContainsPoint(float x, float y); - nsresult GetGlobalTransform(gfxContext *aContext); + PRBool GetGlobalTransform(gfxMatrix *aContext); nsresult GetHighlight(PRUint32 *charnum, PRUint32 *nchars, nscolor *foreground, nscolor *background); - nsRefPtr mFontGroup; - nsAutoPtr mFontStyle; + // Owning pointer, must call gfxTextRunWordCache::RemoveTextRun before deleting + gfxTextRun *mTextRun; gfxPoint mPosition; PRUint8 mWhitespaceHandling; }; diff --git a/layout/svg/base/src/nsSVGTextFrame.cpp b/layout/svg/base/src/nsSVGTextFrame.cpp index 935a6bc5c3a8..2e0b3864303e 100644 --- a/layout/svg/base/src/nsSVGTextFrame.cpp +++ b/layout/svg/base/src/nsSVGTextFrame.cpp @@ -73,15 +73,6 @@ NS_NewSVGTextFrame(nsIPresShell* aPresShell, nsIContent* aContent, nsStyleContex //---------------------------------------------------------------------- // nsIFrame methods -NS_IMETHODIMP -nsSVGTextFrame::SetInitialChildList(nsIAtom* aListName, - nsIFrame* aChildList) -{ - nsresult rv = nsSVGTextFrameBase::SetInitialChildList(aListName, aChildList); - NotifyGlyphMetricsChange(); - return rv; -} - NS_IMETHODIMP nsSVGTextFrame::AttributeChanged(PRInt32 aNameSpaceID, nsIAtom* aAttribute, @@ -127,7 +118,7 @@ nsSVGTextFrame::GetType() const NS_IMETHODIMP nsSVGTextFrame::GetNumberOfChars(PRInt32 *_retval) { - UpdateGlyphPositioning(); + UpdateGlyphPositioning(PR_FALSE); return nsSVGTextFrameBase::GetNumberOfChars(_retval); } @@ -135,7 +126,7 @@ nsSVGTextFrame::GetNumberOfChars(PRInt32 *_retval) NS_IMETHODIMP nsSVGTextFrame::GetComputedTextLength(float *_retval) { - UpdateGlyphPositioning(); + UpdateGlyphPositioning(PR_FALSE); return nsSVGTextFrameBase::GetComputedTextLength(_retval); } @@ -143,7 +134,7 @@ nsSVGTextFrame::GetComputedTextLength(float *_retval) NS_IMETHODIMP nsSVGTextFrame::GetSubStringLength(PRUint32 charnum, PRUint32 nchars, float *_retval) { - UpdateGlyphPositioning(); + UpdateGlyphPositioning(PR_FALSE); return nsSVGTextFrameBase::GetSubStringLength(charnum, nchars, _retval); } @@ -151,7 +142,7 @@ nsSVGTextFrame::GetSubStringLength(PRUint32 charnum, PRUint32 nchars, float *_re NS_IMETHODIMP nsSVGTextFrame::GetStartPositionOfChar(PRUint32 charnum, nsIDOMSVGPoint **_retval) { - UpdateGlyphPositioning(); + UpdateGlyphPositioning(PR_FALSE); return nsSVGTextFrameBase::GetStartPositionOfChar(charnum, _retval); } @@ -159,7 +150,7 @@ nsSVGTextFrame::GetStartPositionOfChar(PRUint32 charnum, nsIDOMSVGPoint **_retva NS_IMETHODIMP nsSVGTextFrame::GetEndPositionOfChar(PRUint32 charnum, nsIDOMSVGPoint **_retval) { - UpdateGlyphPositioning(); + UpdateGlyphPositioning(PR_FALSE); return nsSVGTextFrameBase::GetEndPositionOfChar(charnum, _retval); } @@ -167,7 +158,7 @@ nsSVGTextFrame::GetEndPositionOfChar(PRUint32 charnum, nsIDOMSVGPoint **_retval) NS_IMETHODIMP nsSVGTextFrame::GetExtentOfChar(PRUint32 charnum, nsIDOMSVGRect **_retval) { - UpdateGlyphPositioning(); + UpdateGlyphPositioning(PR_FALSE); return nsSVGTextFrameBase::GetExtentOfChar(charnum, _retval); } @@ -175,7 +166,7 @@ nsSVGTextFrame::GetExtentOfChar(PRUint32 charnum, nsIDOMSVGRect **_retval) NS_IMETHODIMP nsSVGTextFrame::GetRotationOfChar(PRUint32 charnum, float *_retval) { - UpdateGlyphPositioning(); + UpdateGlyphPositioning(PR_FALSE); return nsSVGTextFrameBase::GetRotationOfChar(charnum, _retval); } @@ -183,7 +174,7 @@ nsSVGTextFrame::GetRotationOfChar(PRUint32 charnum, float *_retval) NS_IMETHODIMP nsSVGTextFrame::GetCharNumAtPosition(nsIDOMSVGPoint *point, PRInt32 *_retval) { - UpdateGlyphPositioning(); + UpdateGlyphPositioning(PR_FALSE); return nsSVGTextFrameBase::GetCharNumAtPosition(point, _retval); } @@ -225,7 +216,6 @@ NS_IMETHODIMP nsSVGTextFrame::NotifyRedrawUnsuspended() { mMetricsState = unsuspended; - UpdateGlyphPositioning(); return nsSVGTextFrameBase::NotifyRedrawUnsuspended(); } @@ -252,10 +242,34 @@ nsSVGTextFrame::GetOverrideCTM() return matrix; } +NS_IMETHODIMP +nsSVGTextFrame::PaintSVG(nsSVGRenderState* aContext, nsRect *aDirtyRect) +{ + UpdateGlyphPositioning(PR_TRUE); + + return nsSVGTextFrameBase::PaintSVG(aContext, aDirtyRect); +} + +NS_IMETHODIMP +nsSVGTextFrame::GetFrameForPointSVG(float x, float y, nsIFrame** hit) +{ + UpdateGlyphPositioning(PR_TRUE); + + return nsSVGTextFrameBase::GetFrameForPointSVG(x, y, hit); +} + +NS_IMETHODIMP +nsSVGTextFrame::UpdateCoveredRegion() +{ + UpdateGlyphPositioning(PR_TRUE); + + return nsSVGTextFrameBase::UpdateCoveredRegion(); +} + NS_IMETHODIMP nsSVGTextFrame::GetBBox(nsIDOMSVGRect **_retval) { - UpdateGlyphPositioning(); + UpdateGlyphPositioning(PR_TRUE); return nsSVGTextFrameBase::GetBBox(_retval); } @@ -308,7 +322,6 @@ void nsSVGTextFrame::NotifyGlyphMetricsChange() { mPositioningDirty = PR_TRUE; - UpdateGlyphPositioning(); } static void @@ -332,7 +345,7 @@ GetSingleValue(nsISVGGlyphFragmentLeaf *fragment, } void -nsSVGTextFrame::UpdateGlyphPositioning() +nsSVGTextFrame::UpdateGlyphPositioning(PRBool aForceGlobalTransform) { if (mMetricsState == suspended || !mPositioningDirty) return; @@ -425,7 +438,7 @@ nsSVGTextFrame::UpdateGlyphPositioning() float dx = 0.0f; nsCOMPtr list = fragment->GetDx(); GetSingleValue(fragment, list, &dx); - chunkLength += dx + fragment->GetAdvance(); + chunkLength += dx + fragment->GetAdvance(aForceGlobalTransform); fragment = fragment->GetNextGlyphFragment(); if (fragment && fragment->IsAbsolutelyPositioned()) break; @@ -452,10 +465,11 @@ nsSVGTextFrame::UpdateGlyphPositioning() GetSingleValue(fragment, list, &dy); } - float baseline_offset = fragment->GetBaselineOffset(baseline); + float baseline_offset = + fragment->GetBaselineOffset(baseline, aForceGlobalTransform); fragment->SetGlyphPosition(x + dx, y + dy - baseline_offset); - x += dx + fragment->GetAdvance(); + x += dx + fragment->GetAdvance(aForceGlobalTransform); y += dy; fragment = fragment->GetNextGlyphFragment(); if (fragment && fragment->IsAbsolutelyPositioned()) diff --git a/layout/svg/base/src/nsSVGTextFrame.h b/layout/svg/base/src/nsSVGTextFrame.h index 8ab3a438660e..f51834364d97 100755 --- a/layout/svg/base/src/nsSVGTextFrame.h +++ b/layout/svg/base/src/nsSVGTextFrame.h @@ -52,12 +52,10 @@ protected: : nsSVGTextFrameBase(aContext), mMetricsState(unsuspended), mPropagateTransform(PR_TRUE), - mPositioningDirty(PR_FALSE) {} + mPositioningDirty(PR_TRUE) {} public: // nsIFrame: - NS_IMETHOD SetInitialChildList(nsIAtom* aListName, - nsIFrame* aChildList); NS_IMETHOD AttributeChanged(PRInt32 aNameSpaceID, nsIAtom* aAttribute, PRInt32 aModType); @@ -84,6 +82,11 @@ public: virtual void NotifySVGChanged(PRUint32 aFlags); NS_IMETHOD NotifyRedrawSuspended(); NS_IMETHOD NotifyRedrawUnsuspended(); + // Override these four to ensure that UpdateGlyphPositioning is called + // to bring glyph positions up to date + NS_IMETHOD PaintSVG(nsSVGRenderState* aContext, nsRect *aDirtyRect); + NS_IMETHOD GetFrameForPointSVG(float x, float y, nsIFrame** hit); + NS_IMETHOD UpdateCoveredRegion(); NS_IMETHOD GetBBox(nsIDOMSVGRect **_retval); // nsSVGContainerFrame methods: @@ -103,7 +106,12 @@ public: void NotifyGlyphMetricsChange(); private: - void UpdateGlyphPositioning(); + /** + * @param aForceGlobalTransform passed down to nsSVGGlyphFrames to + * control whether they should use the global transform even when + * NS_STATE_NONDISPLAY_CHILD + */ + void UpdateGlyphPositioning(PRBool aForceGlobalTransform); nsCOMPtr mCanvasTM; nsCOMPtr mOverrideCTM;