/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* ***** BEGIN LICENSE BLOCK ***** * Version: MPL 1.1/GPL 2.0/LGPL 2.1 * * The contents of this file are subject to the Mozilla Public License Version * 1.1 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * http://www.mozilla.org/MPL/ * * Software distributed under the License is distributed on an "AS IS" basis, * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License * for the specific language governing rights and limitations under the * License. * * The Original Code is the Mozilla SVG project. * * The Initial Developer of the Original Code is * Crocodile Clips Ltd.. * Portions created by the Initial Developer are Copyright (C) 2002 * the Initial Developer. All Rights Reserved. * * Contributor(s): * Alex Fritze (original author) * * Alternatively, the contents of this file may be used under the terms of * either of the GNU General Public License Version 2 or later (the "GPL"), * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), * in which case the provisions of the GPL or the LGPL are applicable instead * of those above. If you wish to allow use of your version of this file only * under the terms of either the GPL or the LGPL, and not to allow others to * use your version of this file under the terms of the MPL, indicate your * decision by deleting the provisions above and replace them with the notice * and other provisions required by the GPL or the LGPL. If you do not delete * the provisions above, a recipient may use your version of this file under * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ #include "nsSVGTextFrame.h" #include "nsILookAndFeel.h" #include "nsTextFragment.h" #include "nsSVGUtils.h" #include "nsIDOMSVGLengthList.h" #include "nsIDOMSVGLength.h" #include "nsIDOMSVGRect.h" #include "nsIDOMSVGPoint.h" #include "nsSVGGlyphFrame.h" #include "nsSVGTextPathFrame.h" #include "nsSVGPathElement.h" #include "nsSVGPoint.h" #include "nsSVGRect.h" #include "nsDOMError.h" #include "gfxContext.h" #include "gfxMatrix.h" #include "gfxPlatform.h" #include "gfxTextRunWordCache.h" 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 SetInitialMatrix(gfxContext *aContext) { mInitialMatrix = aContext->CurrentMatrix(); if (mInitialMatrix.IsSingular()) { mInError = PR_TRUE; } } /** * Try to set up aContext so we can draw the whole textrun at once. * This applies any global transform requested by SetInitialMatrix, * 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 SetInitialMatrix, * 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); } /** * 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 SetInitialMatrix, 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 SetInitialMatrix, 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 nsIFrame* NS_NewSVGGlyphFrame(nsIPresShell* aPresShell, nsIContent* aContent, nsIFrame* parentFrame, nsStyleContext* aContext) { NS_ASSERTION(parentFrame, "null parent"); nsISVGTextContentMetrics *metrics; CallQueryInterface(parentFrame, &metrics); NS_ASSERTION(metrics, "trying to construct an SVGGlyphFrame for an invalid container"); NS_ASSERTION(aContent->IsNodeOfType(nsINode::eTEXT), "trying to construct an SVGGlyphFrame for wrong content element"); return new (aPresShell) nsSVGGlyphFrame(aContext); } //---------------------------------------------------------------------- // nsISupports methods NS_INTERFACE_MAP_BEGIN(nsSVGGlyphFrame) NS_INTERFACE_MAP_ENTRY(nsISVGGlyphFragmentLeaf) NS_INTERFACE_MAP_ENTRY(nsISVGGlyphFragmentNode) NS_INTERFACE_MAP_ENTRY(nsISVGChildFrame) NS_INTERFACE_MAP_END_INHERITING(nsSVGGlyphFrameBase) //---------------------------------------------------------------------- // nsIFrame methods NS_IMETHODIMP nsSVGGlyphFrame::CharacterDataChanged(nsPresContext* aPresContext, nsIContent* aChild, PRBool aAppend) { ClearTextRun(); NotifyGlyphMetricsChange(); 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(); ClearTextRun(); NotifyGlyphMetricsChange(); return NS_OK; } NS_IMETHODIMP nsSVGGlyphFrame::SetSelected(nsPresContext* aPresContext, nsIDOMRange* aRange, PRBool aSelected, nsSpread aSpread) { #if defined(DEBUG) && defined(SVG_DEBUG_SELECTION) printf("nsSVGGlyphFrame(%p)::SetSelected()\n", this); #endif // return nsSVGGlyphFrameBase::SetSelected(aPresContext, aRange, aSelected, aSpread); // check whether style allows selection PRBool selectable; IsSelectable(&selectable, nsnull); if (!selectable) return NS_OK; if ( aSelected ){ mState |= NS_FRAME_SELECTED_CONTENT; } else mState &= ~NS_FRAME_SELECTED_CONTENT; nsSVGUtils::UpdateGraphic(this); return NS_OK; } NS_IMETHODIMP nsSVGGlyphFrame::GetSelected(PRBool *aSelected) const { nsresult rv = nsSVGGlyphFrameBase::GetSelected(aSelected); #if defined(DEBUG) && defined(SVG_DEBUG_SELECTION) printf("nsSVGGlyphFrame(%p)::GetSelected()=%d\n", this, *aSelected); #endif return rv; } NS_IMETHODIMP nsSVGGlyphFrame::IsSelectable(PRBool* aIsSelectable, PRUint8* aSelectStyle) const { nsresult rv = nsSVGGlyphFrameBase::IsSelectable(aIsSelectable, aSelectStyle); #if defined(DEBUG) && defined(SVG_DEBUG_SELECTION) printf("nsSVGGlyphFrame(%p)::IsSelectable()=(%d,%d)\n", this, *aIsSelectable, aSelectStyle); #endif return rv; } nsIAtom * nsSVGGlyphFrame::GetType() const { return nsGkAtoms::svgGlyphFrame; } //---------------------------------------------------------------------- // nsISVGChildFrame methods NS_IMETHODIMP nsSVGGlyphFrame::PaintSVG(nsSVGRenderState *aContext, nsRect *aDirtyRect) { if (!GetStyleVisibility()->IsVisible()) return NS_OK; gfxContext *gfx = aContext->GetGfxContext(); PRUint16 renderMode = aContext->GetRenderMode(); if (renderMode != nsSVGRenderState::NORMAL) { gfxMatrix matrix = gfx->CurrentMatrix(); SetupGlobalTransform(gfx); CharacterIterator iter(this, PR_TRUE); iter.SetInitialMatrix(gfx); if (GetClipRule() == NS_STYLE_FILL_RULE_EVENODD) gfx->SetFillRule(gfxContext::FILL_RULE_EVEN_ODD); else gfx->SetFillRule(gfxContext::FILL_RULE_WINDING); if (renderMode == nsSVGRenderState::CLIP_MASK) { gfx->SetAntialiasMode(gfxContext::MODE_ALIASED); gfx->SetColor(gfxRGBA(1.0f, 1.0f, 1.0f, 1.0f)); FillCharacters(&iter, gfx); } else { AddCharactersToPath(&iter, gfx); } gfx->SetMatrix(matrix); return NS_OK; } // We are adding patterns or gradients to the context. Save // it so we don't leak them into the next object we draw gfx->Save(); SetupGlobalTransform(gfx); if (HasFill() && SetupCairoFill(gfx)) { gfxMatrix matrix = gfx->CurrentMatrix(); CharacterIterator iter(this, PR_TRUE); iter.SetInitialMatrix(gfx); FillCharacters(&iter, gfx); gfx->SetMatrix(matrix); } if (HasStroke() && SetupCairoStroke(gfx)) { // SetupCairoStroke will clear mTextRun whenever // there is a pattern or gradient on the text CharacterIterator iter(this, PR_TRUE); iter.SetInitialMatrix(gfx); gfx->NewPath(); 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; } NS_IMETHODIMP nsSVGGlyphFrame::GetFrameForPointSVG(float x, float y, nsIFrame** hit) { #ifdef DEBUG //printf("nsSVGGlyphFrame(%p)::GetFrameForPoint\n", this); #endif // test for hit: *hit = nsnull; if (!mRect.Contains(nscoord(x), nscoord(y))) return NS_OK; PRBool events = PR_FALSE; switch (GetStyleSVG()->mPointerEvents) { case NS_STYLE_POINTER_EVENTS_NONE: break; case NS_STYLE_POINTER_EVENTS_VISIBLEPAINTED: if (GetStyleVisibility()->IsVisible() && (GetStyleSVG()->mFill.mType != eStyleSVGPaintType_None || GetStyleSVG()->mStroke.mType != eStyleSVGPaintType_None)) events = PR_TRUE; break; case NS_STYLE_POINTER_EVENTS_VISIBLEFILL: case NS_STYLE_POINTER_EVENTS_VISIBLESTROKE: case NS_STYLE_POINTER_EVENTS_VISIBLE: if (GetStyleVisibility()->IsVisible()) events = PR_TRUE; break; case NS_STYLE_POINTER_EVENTS_PAINTED: if (GetStyleSVG()->mFill.mType != eStyleSVGPaintType_None || GetStyleSVG()->mStroke.mType != eStyleSVGPaintType_None) events = PR_TRUE; break; case NS_STYLE_POINTER_EVENTS_FILL: case NS_STYLE_POINTER_EVENTS_STROKE: case NS_STYLE_POINTER_EVENTS_ALL: events = PR_TRUE; break; default: NS_ERROR("not reached"); break; } if (!events) return NS_OK; PRBool isHit = ContainsPoint(x, y); if (isHit) *hit = this; return NS_OK; } NS_IMETHODIMP_(nsRect) nsSVGGlyphFrame::GetCoveredRegion() { return mRect; } static gfxContext * MakeTmpCtx() { return new gfxContext(nsSVGUtils::GetThebesComputationalSurface()); } NS_IMETHODIMP nsSVGGlyphFrame::UpdateCoveredRegion() { nsRefPtr tmpCtx = MakeTmpCtx(); SetupGlobalTransform(tmpCtx); CharacterIterator iter(this, PR_TRUE); iter.SetInitialMatrix(tmpCtx); gfxRect 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 { extent = gfxRect(0, 0, 0, 0); } mRect = nsSVGUtils::ToBoundingPixelRect(extent); return NS_OK; } NS_IMETHODIMP nsSVGGlyphFrame::InitialUpdate() { NS_ASSERTION(GetStateBits() & NS_FRAME_FIRST_REFLOW, "Yikes! We've been called already! Hopefully we weren't called " "before our nsSVGOuterSVGFrame's initial Reflow()!!!"); NS_ASSERTION(!(mState & NS_FRAME_IN_REFLOW), "We don't actually participate in reflow"); NotifyGlyphMetricsChange(); // Do unset the various reflow bits, though. mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN); return NS_OK; } void nsSVGGlyphFrame::NotifySVGChanged(PRUint32 aFlags) { if (aFlags & TRANSFORM_CHANGED) { ClearTextRun(); } if (!(aFlags & SUPPRESS_INVALIDATION)) { nsSVGUtils::UpdateGraphic(this); } } NS_IMETHODIMP nsSVGGlyphFrame::NotifyRedrawSuspended() { // XXX should we cache the fact that redraw is suspended? return NS_OK; } NS_IMETHODIMP nsSVGGlyphFrame::NotifyRedrawUnsuspended() { if (GetStateBits() & NS_STATE_SVG_DIRTY) nsSVGUtils::UpdateGraphic(this); 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; nsRefPtr tmpCtx = MakeTmpCtx(); SetupGlobalTransform(tmpCtx); CharacterIterator iter(this, PR_TRUE); iter.SetInitialMatrix(tmpCtx); AddCharactersToPath(&iter, tmpCtx); tmpCtx->IdentityMatrix(); return NS_NewSVGRect(_retval, tmpCtx->GetUserPathExtent()); } //---------------------------------------------------------------------- // nsSVGGeometryFrame methods: /* readonly attribute nsIDOMSVGMatrix canvasTM; */ NS_IMETHODIMP nsSVGGlyphFrame::GetCanvasTM(nsIDOMSVGMatrix * *aCTM) { NS_ASSERTION(mParent, "null parent"); nsSVGContainerFrame *containerFrame = static_cast (mParent); nsCOMPtr parentTM = containerFrame->GetCanvasTM(); *aCTM = nsnull; parentTM.swap(*aCTM); return NS_OK; } //---------------------------------------------------------------------- // nsSVGGlyphFrame methods: PRBool nsSVGGlyphFrame::GetCharacterData(nsAString & aCharacterData) { nsAutoString characterData; mContent->AppendTextTo(characterData); if (mWhitespaceHandling & COMPRESS_WHITESPACE) { PRBool trimLeadingWhitespace, trimTrailingWhitespace; trimLeadingWhitespace = ((mWhitespaceHandling & TRIM_LEADING_WHITESPACE) != 0); trimTrailingWhitespace = ((mWhitespaceHandling & TRIM_TRAILING_WHITESPACE) != 0); characterData.CompressWhitespace(trimLeadingWhitespace, trimTrailingWhitespace); } else { nsAString::iterator start, end; characterData.BeginWriting(start); characterData.EndWriting(end); while (start != end) { if (NS_IsAsciiWhitespace(*start)) *start = ' '; ++start; } } aCharacterData = characterData; return !characterData.IsEmpty(); } PRBool nsSVGGlyphFrame::GetCharacterPositions(nsTArray* aCharacterPositions, float aMetricsScale) { 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 PR_TRUE; nsRefPtr data = textPath->GetFlattenedPath(); /* textPath frame, but invalid target */ if (!data) return PR_FALSE; gfxFloat length = data->GetLength(); PRUint32 strLength = mTextRun->GetLength(); if (!aCharacterPositions->SetLength(strLength)) return PR_FALSE; 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 = mTextRun->GetAdvanceWidth(i, 1, nsnull)*aMetricsScale / 2.0; /* have we run off the end of the path? */ if (x + halfAdvance > length) break; /* check that we've advanced to the start of the path */ if (x + halfAdvance >= 0.0) { cp[i].draw = PR_TRUE; // add y (normal) // add rotation // move point back along tangent gfxPoint pt = data->FindPoint(gfxPoint(x + halfAdvance, mPosition.y), &(cp[i].angle)); cp[i].pos = pt - gfxPoint(cos(cp[i].angle), sin(cp[i].angle)) * halfAdvance; } x += 2 * halfAdvance; } return PR_TRUE; } //---------------------------------------------------------------------- // Utilities for converting from indices in the uncompressed content // element strings to compressed frame string and back: static int CompressIndex(int index, const nsTextFragment*fragment) { int ci=0; if (fragment->Is2b()) { const PRUnichar *data=fragment->Get2b(); while(*data && index) { if (XP_IS_SPACE_W(*data)){ do { ++data; --index; }while(XP_IS_SPACE_W(*data) && index); } else { ++data; --index; } ++ci; } } else { const char *data=fragment->Get1b(); while(*data && index) { if (XP_IS_SPACE_W(*data)){ do { ++data; --index; }while(XP_IS_SPACE_W(*data) && index); } else { ++data; --index; } ++ci; } } return ci; } static int UncompressIndex(int index, PRBool bRightAffinity, const nsTextFragment*fragment) { // XXX return index; } nsresult nsSVGGlyphFrame::GetHighlight(PRUint32 *charnum, PRUint32 *nchars, nscolor *foreground, nscolor *background) { *foreground = NS_RGB(255,255,255); *background = NS_RGB(0,0,0); *charnum=0; *nchars=0; PRBool hasHighlight = (mState & NS_FRAME_SELECTED_CONTENT) == NS_FRAME_SELECTED_CONTENT; if (!hasHighlight) { NS_ERROR("nsSVGGlyphFrame::GetHighlight() called by renderer when there is no highlight"); return NS_ERROR_FAILURE; } nsPresContext *presContext = PresContext(); // The selection ranges are relative to the uncompressed text in // the content element. We'll need the text fragment: const nsTextFragment *fragment = mContent->GetText(); NS_ASSERTION(fragment, "no text"); // get the selection details SelectionDetails *details = nsnull; { nsCOMPtr frameSelection; { nsCOMPtr controller; GetSelectionController(presContext, getter_AddRefs(controller)); if (!controller) { NS_ERROR("no selection controller"); return NS_ERROR_FAILURE; } frameSelection = do_QueryInterface(controller); } if (!frameSelection) { frameSelection = presContext->PresShell()->FrameSelection(); } if (!frameSelection) { NS_ERROR("no frameselection interface"); return NS_ERROR_FAILURE; } details = frameSelection->LookUpSelection( mContent, 0, fragment->GetLength(), PR_FALSE ); } #if defined(DEBUG) && defined(SVG_DEBUG_SELECTION) { SelectionDetails *dp = details; printf("nsSVGGlyphFrame(%p)::GetHighlight() [\n", this); while (dp) { printf("selection detail: %d(%d)->%d(%d) type %d\n", dp->mStart, CompressIndex(dp->mStart, fragment), dp->mEnd, CompressIndex(dp->mEnd, fragment), dp->mType); dp = dp->mNext; } printf("]\n"); } #endif if (details) { NS_ASSERTION(details->mNext==nsnull, "can't do multiple selection ranges"); *charnum=CompressIndex(details->mStart, fragment); *nchars=CompressIndex(details->mEnd, fragment)-*charnum; nsILookAndFeel *look = presContext->LookAndFeel(); look->GetColor(nsILookAndFeel::eColor_TextSelectBackground, *background); look->GetColor(nsILookAndFeel::eColor_TextSelectForeground, *foreground); SelectionDetails *dp = details; while ((dp=details->mNext) != nsnull) { delete details; details = dp; } delete details; } return NS_OK; } //---------------------------------------------------------------------- // nsISVGGlyphFragmentLeaf interface: NS_IMETHODIMP_(void) nsSVGGlyphFrame::SetGlyphPosition(float x, float y) { mPosition.MoveTo(x, y); nsSVGUtils::UpdateGraphic(this); } NS_IMETHODIMP nsSVGGlyphFrame::GetStartPositionOfChar(PRUint32 charnum, nsIDOMSVGPoint **_retval) { *_retval = nsnull; CharacterIterator iter(this, PR_FALSE); if (!iter.AdvanceToCharacter(charnum)) return NS_ERROR_DOM_INDEX_SIZE_ERR; return NS_NewSVGPoint(_retval, iter.GetPositionData().pos); } NS_IMETHODIMP nsSVGGlyphFrame::GetEndPositionOfChar(PRUint32 charnum, nsIDOMSVGPoint **_retval) { *_retval = nsnull; CharacterIterator iter(this, PR_FALSE); if (!iter.AdvanceToCharacter(charnum)) return NS_ERROR_DOM_INDEX_SIZE_ERR; 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 nsSVGGlyphFrame::GetExtentOfChar(PRUint32 charnum, nsIDOMSVGRect **_retval) { *_retval = nsnull; CharacterIterator iter(this, PR_FALSE); if (!iter.AdvanceToCharacter(charnum)) return NS_ERROR_DOM_INDEX_SIZE_ERR; gfxTextRun::Metrics metrics = mTextRun->MeasureText(charnum, 1, PR_FALSE, nsnull, nsnull); 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; *_retval = float(pos.angle / radPerDeg); return NS_OK; } NS_IMETHODIMP_(float) nsSVGGlyphFrame::GetBaselineOffset(PRUint16 baselineIdentifier, PRBool aForceGlobalTransform) { float drawScale, metricsScale; if (!EnsureTextRun(&drawScale, &metricsScale, aForceGlobalTransform)) return 0.0f; gfxTextRun::Metrics metrics = 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: baselineAppUnits = -metrics.mAscent; break; case BASELINE_TEXT_AFTER_EDGE: baselineAppUnits = metrics.mDescent; break; case BASELINE_CENTRAL: case BASELINE_MIDDLE: baselineAppUnits = -(metrics.mAscent - metrics.mDescent) / 2.0f; break; case BASELINE_ALPHABETIC: default: baselineAppUnits = 0.0; break; } return float(baselineAppUnits)*metricsScale; } NS_IMETHODIMP_(float) nsSVGGlyphFrame::GetAdvance(PRBool aForceGlobalTransform) { float drawScale, metricsScale; if (!EnsureTextRun(&drawScale, &metricsScale, aForceGlobalTransform)) return 0.0f; gfxFloat advanceAppUnits = mTextRun->GetAdvanceWidth(0, mTextRun->GetLength(), nsnull); return float(advanceAppUnits)*metricsScale; } NS_IMETHODIMP_(nsSVGTextPathFrame*) nsSVGGlyphFrame::FindTextPathParent() { /* check if we're the child of a textPath */ for (nsIFrame *frame = GetParent(); frame != nsnull; frame = frame->GetParent()) { nsIAtom* type = frame->GetType(); if (type == nsGkAtoms::svgTextPathFrame) { return static_cast(frame); } else if (type == nsGkAtoms::svgTextFrame) return nsnull; } return nsnull; } NS_IMETHODIMP_(PRBool) nsSVGGlyphFrame::IsStartOfChunk() { // this fragment is a chunk if it has a corresponding absolute // position adjustment in an ancestors' x or y array. (At the moment // we don't map the full arrays, but only the first elements.) return PR_FALSE; } NS_IMETHODIMP_(void) nsSVGGlyphFrame::GetAdjustedPosition(/* inout */ float &x, /* inout */ float &y) { } NS_IMETHODIMP_(already_AddRefed) nsSVGGlyphFrame::GetX() { nsSVGTextContainerFrame *containerFrame; containerFrame = static_cast(mParent); if (containerFrame) return containerFrame->GetX(); return nsnull; } NS_IMETHODIMP_(already_AddRefed) nsSVGGlyphFrame::GetY() { nsSVGTextContainerFrame *containerFrame; containerFrame = static_cast(mParent); if (containerFrame) return containerFrame->GetY(); return nsnull; } NS_IMETHODIMP_(already_AddRefed) nsSVGGlyphFrame::GetDx() { nsSVGTextContainerFrame *containerFrame; containerFrame = static_cast(mParent); if (containerFrame) return containerFrame->GetDx(); return nsnull; } NS_IMETHODIMP_(already_AddRefed) nsSVGGlyphFrame::GetDy() { nsSVGTextContainerFrame *containerFrame; containerFrame = static_cast(mParent); if (containerFrame) return containerFrame->GetDy(); return nsnull; } NS_IMETHODIMP_(PRUint16) nsSVGGlyphFrame::GetTextAnchor() { return GetStyleSVG()->mTextAnchor; } NS_IMETHODIMP_(PRBool) nsSVGGlyphFrame::IsAbsolutelyPositioned() { nsIFrame *lastFrame = this; for (nsIFrame *frame = GetParent(); frame != nsnull; lastFrame = frame, frame = frame->GetParent()) { /* need to be the first child if we are absolutely positioned */ if (!frame || frame->GetFirstChild(nsnull) != lastFrame) break; // textPath is always absolutely positioned for our purposes if (frame->GetType() == nsGkAtoms::svgTextPathFrame) return PR_TRUE; if (frame && (frame->GetContent()->HasAttr(kNameSpaceID_None, nsGkAtoms::x) || frame->GetContent()->HasAttr(kNameSpaceID_None, nsGkAtoms::y))) return PR_TRUE; if (frame->GetType() == nsGkAtoms::svgTextFrame) break; } return PR_FALSE; } //---------------------------------------------------------------------- // nsISVGGlyphFragmentNode interface: NS_IMETHODIMP_(PRUint32) nsSVGGlyphFrame::GetNumberOfChars() { if (mWhitespaceHandling == PRESERVE_WHITESPACE) return mContent->TextLength(); nsAutoString text; GetCharacterData(text); return text.Length(); } NS_IMETHODIMP_(float) nsSVGGlyphFrame::GetComputedTextLength() { return GetAdvance(PR_FALSE); } NS_IMETHODIMP_(float) nsSVGGlyphFrame::GetSubStringLength(PRUint32 charnum, PRUint32 fragmentChars) { float drawScale, metricsScale; if (!EnsureTextRun(&drawScale, &metricsScale, PR_FALSE)) return 0.0f; gfxFloat advanceAppUnits = mTextRun->GetAdvanceWidth(charnum, fragmentChars, nsnull); return float(advanceAppUnits)*metricsScale; } NS_IMETHODIMP_(PRInt32) nsSVGGlyphFrame::GetCharNumAtPosition(nsIDOMSVGPoint *point) { float xPos, yPos; point->GetX(&xPos); point->GetY(&yPos); 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 = 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; } } return last; } NS_IMETHODIMP_(nsISVGGlyphFragmentLeaf *) nsSVGGlyphFrame::GetFirstGlyphFragment() { return this; } NS_IMETHODIMP_(nsISVGGlyphFragmentLeaf *) nsSVGGlyphFrame::GetNextGlyphFragment() { nsIFrame* sibling = mNextSibling; while (sibling) { nsISVGGlyphFragmentNode *node = nsnull; CallQueryInterface(sibling, &node); if (node) return node->GetFirstGlyphFragment(); sibling = sibling->GetNextSibling(); } // no more siblings. go back up the tree. NS_ASSERTION(mParent, "null parent"); nsISVGGlyphFragmentNode *node = nsnull; CallQueryInterface(mParent, &node); return node ? node->GetNextGlyphFragment() : nsnull; } NS_IMETHODIMP_(void) nsSVGGlyphFrame::SetWhitespaceHandling(PRUint8 aWhitespaceHandling) { mWhitespaceHandling = aWhitespaceHandling; } //---------------------------------------------------------------------- // void nsSVGGlyphFrame::NotifyGlyphMetricsChange() { nsSVGTextContainerFrame *containerFrame = static_cast(mParent); if (containerFrame) containerFrame->NotifyGlyphMetricsChange(); } PRBool nsSVGGlyphFrame::ContainsPoint(float x, float y) { nsRefPtr tmpCtx = MakeTmpCtx(); SetupGlobalTransform(tmpCtx); CharacterIterator iter(this, PR_TRUE); iter.SetInitialMatrix(tmpCtx); PRInt32 i; while ((i = iter.NextChar()) >= 0) { gfxTextRun::Metrics metrics = mTextRun->MeasureText(i, 1, PR_FALSE, nsnull, nsnull); iter.SetupForMetrics(tmpCtx); tmpCtx->Rectangle(metrics.mBoundingBox); } tmpCtx->IdentityMatrix(); return tmpCtx->PointInFill(gfxPoint(x, y)); } PRBool nsSVGGlyphFrame::GetGlobalTransform(gfxMatrix *aMatrix) { nsCOMPtr ctm; GetCanvasTM(getter_AddRefs(ctm)); if (!ctm) return PR_FALSE; *aMatrix = nsSVGUtils::ConvertSVGMatrixToThebes(ctm); return !aMatrix->IsSingular(); } void nsSVGGlyphFrame::SetupGlobalTransform(gfxContext *aContext) { gfxMatrix matrix; GetGlobalTransform(&matrix); aContext->Multiply(matrix); } void nsSVGGlyphFrame::ClearTextRun() { if (!mTextRun) return; gfxTextRunWordCache::RemoveTextRun(mTextRun); delete mTextRun; mTextRun = nsnull; } 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()); // 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 CharacterIterator::CharacterIterator(nsSVGGlyphFrame *aSource, PRBool aForceGlobalTransform) : mSource(aSource), mCurrentAdvance(0), mCurrentChar(-1), mInError(PR_FALSE) { if (!aSource->EnsureTextRun(&mDrawScale, &mMetricsScale, aForceGlobalTransform) || !aSource->GetCharacterPositions(&mPositions, mMetricsScale)) { mInError = PR_TRUE; } } 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()) { aContext->Translate(mSource->mPosition); aContext->Scale(aScale, aScale); aContext->Translate(gfxPoint(mCurrentAdvance, 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; }