Bug 447567. Instead of brutally wiping out effects properties whenever the style...
[mozilla-central.git] / layout / svg / base / src / nsSVGTextFrame.cpp
bloba4960b529cb6f9b859d2991c9c07d440ffdc0514
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* ***** BEGIN LICENSE BLOCK *****
3 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
5 * The contents of this file are subject to the Mozilla Public License Version
6 * 1.1 (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 * http://www.mozilla.org/MPL/
10 * Software distributed under the License is distributed on an "AS IS" basis,
11 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 * for the specific language governing rights and limitations under the
13 * License.
15 * The Original Code is the Mozilla SVG project.
17 * The Initial Developer of the Original Code is
18 * Crocodile Clips Ltd..
19 * Portions created by the Initial Developer are Copyright (C) 2002
20 * the Initial Developer. All Rights Reserved.
22 * Contributor(s):
23 * Alex Fritze <alex.fritze@crocodile-clips.com> (original author)
25 * Alternatively, the contents of this file may be used under the terms of
26 * either of the GNU General Public License Version 2 or later (the "GPL"),
27 * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 * in which case the provisions of the GPL or the LGPL are applicable instead
29 * of those above. If you wish to allow use of your version of this file only
30 * under the terms of either the GPL or the LGPL, and not to allow others to
31 * use your version of this file under the terms of the MPL, indicate your
32 * decision by deleting the provisions above and replace them with the notice
33 * and other provisions required by the GPL or the LGPL. If you do not delete
34 * the provisions above, a recipient may use your version of this file under
35 * the terms of any one of the MPL, the GPL or the LGPL.
37 * ***** END LICENSE BLOCK ***** */
39 #include "nsIDOMSVGTextElement.h"
40 #include "nsSVGTextFrame.h"
41 #include "nsWeakReference.h"
42 #include "nsIDOMSVGLengthList.h"
43 #include "nsIDOMSVGLength.h"
44 #include "nsIDOMSVGAnimatedNumber.h"
45 #include "nsISVGGlyphFragmentNode.h"
46 #include "nsISVGGlyphFragmentLeaf.h"
47 #include "nsSVGOuterSVGFrame.h"
48 #include "nsIDOMSVGRect.h"
49 #include "nsISVGTextContentMetrics.h"
50 #include "nsSVGRect.h"
51 #include "nsSVGMatrix.h"
52 #include "nsGkAtoms.h"
53 #include "nsSVGTextPathFrame.h"
54 #include "nsSVGPathElement.h"
55 #include "nsSVGUtils.h"
56 #include "nsSVGGraphicElement.h"
58 //----------------------------------------------------------------------
59 // Implementation
61 nsIFrame*
62 NS_NewSVGTextFrame(nsIPresShell* aPresShell, nsIContent* aContent, nsStyleContext* aContext)
64 nsCOMPtr<nsIDOMSVGTextElement> text = do_QueryInterface(aContent);
65 if (!text) {
66 NS_ERROR("Can't create frame! Content is not an SVG text");
67 return nsnull;
70 return new (aPresShell) nsSVGTextFrame(aContext);
73 //----------------------------------------------------------------------
74 // nsIFrame methods
76 NS_IMETHODIMP
77 nsSVGTextFrame::AttributeChanged(PRInt32 aNameSpaceID,
78 nsIAtom* aAttribute,
79 PRInt32 aModType)
81 if (aNameSpaceID != kNameSpaceID_None)
82 return NS_OK;
84 if (aAttribute == nsGkAtoms::transform) {
85 // transform has changed
87 // make sure our cached transform matrix gets (lazily) updated
88 mCanvasTM = nsnull;
90 nsSVGUtils::NotifyChildrenOfSVGChange(this, TRANSFORM_CHANGED);
92 } else if (aAttribute == nsGkAtoms::x ||
93 aAttribute == nsGkAtoms::y ||
94 aAttribute == nsGkAtoms::dx ||
95 aAttribute == nsGkAtoms::dy) {
96 NotifyGlyphMetricsChange();
99 return NS_OK;
102 nsIAtom *
103 nsSVGTextFrame::GetType() const
105 return nsGkAtoms::svgTextFrame;
108 //----------------------------------------------------------------------
109 // nsISVGTextContentMetrics
110 NS_IMETHODIMP
111 nsSVGTextFrame::GetNumberOfChars(PRInt32 *_retval)
113 UpdateGlyphPositioning(PR_FALSE);
115 return nsSVGTextFrameBase::GetNumberOfChars(_retval);
118 NS_IMETHODIMP
119 nsSVGTextFrame::GetComputedTextLength(float *_retval)
121 UpdateGlyphPositioning(PR_FALSE);
123 return nsSVGTextFrameBase::GetComputedTextLength(_retval);
126 NS_IMETHODIMP
127 nsSVGTextFrame::GetSubStringLength(PRUint32 charnum, PRUint32 nchars, float *_retval)
129 UpdateGlyphPositioning(PR_FALSE);
131 return nsSVGTextFrameBase::GetSubStringLength(charnum, nchars, _retval);
134 NS_IMETHODIMP
135 nsSVGTextFrame::GetStartPositionOfChar(PRUint32 charnum, nsIDOMSVGPoint **_retval)
137 UpdateGlyphPositioning(PR_FALSE);
139 return nsSVGTextFrameBase::GetStartPositionOfChar(charnum, _retval);
142 NS_IMETHODIMP
143 nsSVGTextFrame::GetEndPositionOfChar(PRUint32 charnum, nsIDOMSVGPoint **_retval)
145 UpdateGlyphPositioning(PR_FALSE);
147 return nsSVGTextFrameBase::GetEndPositionOfChar(charnum, _retval);
150 NS_IMETHODIMP
151 nsSVGTextFrame::GetExtentOfChar(PRUint32 charnum, nsIDOMSVGRect **_retval)
153 UpdateGlyphPositioning(PR_FALSE);
155 return nsSVGTextFrameBase::GetExtentOfChar(charnum, _retval);
158 NS_IMETHODIMP
159 nsSVGTextFrame::GetRotationOfChar(PRUint32 charnum, float *_retval)
161 UpdateGlyphPositioning(PR_FALSE);
163 return nsSVGTextFrameBase::GetRotationOfChar(charnum, _retval);
166 NS_IMETHODIMP
167 nsSVGTextFrame::GetCharNumAtPosition(nsIDOMSVGPoint *point, PRInt32 *_retval)
169 UpdateGlyphPositioning(PR_FALSE);
171 return nsSVGTextFrameBase::GetCharNumAtPosition(point, _retval);
175 //----------------------------------------------------------------------
176 // nsISVGChildFrame methods
178 void
179 nsSVGTextFrame::NotifySVGChanged(PRUint32 aFlags)
181 if (aFlags & TRANSFORM_CHANGED) {
182 // make sure our cached transform matrix gets (lazily) updated
183 mCanvasTM = nsnull;
186 if (aFlags & COORD_CONTEXT_CHANGED) {
187 // If we are positioned using percentage values we need to update our
188 // position whenever our viewport's dimensions change.
190 // XXX We could check here whether the text frame or any of its children
191 // have any percentage co-ordinates and only update if they don't. This
192 // may not be worth it as we might need to check each glyph
193 NotifyGlyphMetricsChange();
196 nsSVGTextFrameBase::NotifySVGChanged(aFlags);
199 NS_IMETHODIMP
200 nsSVGTextFrame::NotifyRedrawSuspended()
202 mMetricsState = suspended;
204 return nsSVGTextFrameBase::NotifyRedrawSuspended();
207 NS_IMETHODIMP
208 nsSVGTextFrame::NotifyRedrawUnsuspended()
210 mMetricsState = unsuspended;
211 UpdateGlyphPositioning(PR_FALSE);
212 return nsSVGTextFrameBase::NotifyRedrawUnsuspended();
215 NS_IMETHODIMP
216 nsSVGTextFrame::SetMatrixPropagation(PRBool aPropagate)
218 mPropagateTransform = aPropagate;
219 return NS_OK;
222 NS_IMETHODIMP
223 nsSVGTextFrame::SetOverrideCTM(nsIDOMSVGMatrix *aCTM)
225 mOverrideCTM = aCTM;
226 return NS_OK;
229 already_AddRefed<nsIDOMSVGMatrix>
230 nsSVGTextFrame::GetOverrideCTM()
232 nsIDOMSVGMatrix *matrix = mOverrideCTM.get();
233 NS_IF_ADDREF(matrix);
234 return matrix;
237 NS_IMETHODIMP
238 nsSVGTextFrame::PaintSVG(nsSVGRenderState* aContext, nsRect *aDirtyRect)
240 UpdateGlyphPositioning(PR_TRUE);
242 return nsSVGTextFrameBase::PaintSVG(aContext, aDirtyRect);
245 NS_IMETHODIMP
246 nsSVGTextFrame::GetFrameForPointSVG(float x, float y, nsIFrame** hit)
248 UpdateGlyphPositioning(PR_TRUE);
250 return nsSVGTextFrameBase::GetFrameForPointSVG(x, y, hit);
253 NS_IMETHODIMP
254 nsSVGTextFrame::UpdateCoveredRegion()
256 UpdateGlyphPositioning(PR_TRUE);
258 return nsSVGTextFrameBase::UpdateCoveredRegion();
261 NS_IMETHODIMP
262 nsSVGTextFrame::InitialUpdate()
264 nsresult rv = nsSVGTextFrameBase::InitialUpdate();
266 UpdateGlyphPositioning(PR_FALSE);
268 return rv;
271 NS_IMETHODIMP
272 nsSVGTextFrame::GetBBox(nsIDOMSVGRect **_retval)
274 UpdateGlyphPositioning(PR_TRUE);
276 return nsSVGTextFrameBase::GetBBox(_retval);
279 //----------------------------------------------------------------------
280 // nsSVGContainerFrame methods:
282 already_AddRefed<nsIDOMSVGMatrix>
283 nsSVGTextFrame::GetCanvasTM()
285 if (!mPropagateTransform) {
286 nsIDOMSVGMatrix *retval;
287 if (mOverrideCTM) {
288 retval = mOverrideCTM;
289 NS_ADDREF(retval);
290 } else {
291 NS_NewSVGMatrix(&retval);
293 return retval;
296 if (!mCanvasTM) {
297 // get our parent's tm and append local transforms (if any):
298 NS_ASSERTION(mParent, "null parent");
299 nsSVGContainerFrame *containerFrame = static_cast<nsSVGContainerFrame*>
300 (mParent);
301 nsCOMPtr<nsIDOMSVGMatrix> parentTM = containerFrame->GetCanvasTM();
302 NS_ASSERTION(parentTM, "null TM");
304 // got the parent tm, now check for local tm:
305 nsSVGGraphicElement *element =
306 static_cast<nsSVGGraphicElement*>(mContent);
307 nsCOMPtr<nsIDOMSVGMatrix> localTM = element->GetLocalTransformMatrix();
309 if (localTM)
310 parentTM->Multiply(localTM, getter_AddRefs(mCanvasTM));
311 else
312 mCanvasTM = parentTM;
315 nsIDOMSVGMatrix* retval = mCanvasTM.get();
316 NS_IF_ADDREF(retval);
317 return retval;
320 //----------------------------------------------------------------------
323 void
324 nsSVGTextFrame::NotifyGlyphMetricsChange()
326 mPositioningDirty = PR_TRUE;
327 UpdateGlyphPositioning(PR_FALSE);
330 static void
331 GetSingleValue(nsISVGGlyphFragmentLeaf *fragment,
332 nsIDOMSVGLengthList *list, float *val)
334 if (!list)
335 return;
337 PRUint32 count = 0;
338 list->GetNumberOfItems(&count);
339 #ifdef DEBUG
340 if (count > 1)
341 NS_WARNING("multiple lengths for x/y attributes on <text> elements not implemented yet!");
342 #endif
343 if (count) {
344 nsCOMPtr<nsIDOMSVGLength> length;
345 list->GetItem(0, getter_AddRefs(length));
346 length->GetValue(val);
350 void
351 nsSVGTextFrame::UpdateGlyphPositioning(PRBool aForceGlobalTransform)
353 if (mMetricsState == suspended || !mPositioningDirty)
354 return;
356 SetWhitespaceHandling();
358 nsISVGGlyphFragmentNode* node = GetFirstGlyphFragmentChildNode();
359 if (!node) return;
361 mPositioningDirty = PR_FALSE;
363 // we'll align every fragment in this chunk on the dominant-baseline:
364 // XXX should actually inspect 'alignment-baseline' for each fragment
366 PRUint8 baseline;
367 switch(GetStyleSVGReset()->mDominantBaseline) {
368 case NS_STYLE_DOMINANT_BASELINE_TEXT_BEFORE_EDGE:
369 baseline = nsISVGGlyphFragmentLeaf::BASELINE_TEXT_BEFORE_EDGE;
370 break;
371 case NS_STYLE_DOMINANT_BASELINE_TEXT_AFTER_EDGE:
372 baseline = nsISVGGlyphFragmentLeaf::BASELINE_TEXT_AFTER_EDGE;
373 break;
374 case NS_STYLE_DOMINANT_BASELINE_MIDDLE:
375 baseline = nsISVGGlyphFragmentLeaf::BASELINE_MIDDLE;
376 break;
377 case NS_STYLE_DOMINANT_BASELINE_CENTRAL:
378 baseline = nsISVGGlyphFragmentLeaf::BASELINE_CENTRAL;
379 break;
380 case NS_STYLE_DOMINANT_BASELINE_MATHEMATICAL:
381 baseline = nsISVGGlyphFragmentLeaf::BASELINE_MATHEMATICAL;
382 break;
383 case NS_STYLE_DOMINANT_BASELINE_IDEOGRAPHIC:
384 baseline = nsISVGGlyphFragmentLeaf::BASELINE_IDEOGRAPHC;
385 break;
386 case NS_STYLE_DOMINANT_BASELINE_HANGING:
387 baseline = nsISVGGlyphFragmentLeaf::BASELINE_HANGING;
388 break;
389 case NS_STYLE_DOMINANT_BASELINE_AUTO:
390 case NS_STYLE_DOMINANT_BASELINE_USE_SCRIPT:
391 case NS_STYLE_DOMINANT_BASELINE_ALPHABETIC:
392 default:
393 baseline = nsISVGGlyphFragmentLeaf::BASELINE_ALPHABETIC;
394 break;
397 nsISVGGlyphFragmentLeaf *fragment, *firstFragment;
399 firstFragment = node->GetFirstGlyphFragment();
400 if (!firstFragment) {
401 return;
404 float x = 0, y = 0;
407 nsCOMPtr<nsIDOMSVGLengthList> list = GetX();
408 GetSingleValue(firstFragment, list, &x);
411 nsCOMPtr<nsIDOMSVGLengthList> list = GetY();
412 GetSingleValue(firstFragment, list, &y);
415 // loop over chunks
416 while (firstFragment) {
418 nsCOMPtr<nsIDOMSVGLengthList> list = firstFragment->GetX();
419 GetSingleValue(firstFragment, list, &x);
422 nsCOMPtr<nsIDOMSVGLengthList> list = firstFragment->GetY();
423 GetSingleValue(firstFragment, list, &y);
426 // check for startOffset on textPath
427 nsSVGTextPathFrame *textPath = firstFragment->FindTextPathParent();
428 if (textPath) {
429 x = textPath->GetStartOffset();
432 // determine x offset based on text_anchor:
434 PRUint8 anchor = firstFragment->GetTextAnchor();
436 float chunkLength = 0.0f;
437 if (anchor != NS_STYLE_TEXT_ANCHOR_START) {
438 // need to get the total chunk length
440 fragment = firstFragment;
441 while (fragment) {
442 float dx = 0.0f;
443 nsCOMPtr<nsIDOMSVGLengthList> list = fragment->GetDx();
444 GetSingleValue(fragment, list, &dx);
445 chunkLength += dx + fragment->GetAdvance(aForceGlobalTransform);
446 fragment = fragment->GetNextGlyphFragment();
447 if (fragment && fragment->IsAbsolutelyPositioned())
448 break;
452 if (anchor == NS_STYLE_TEXT_ANCHOR_MIDDLE)
453 x -= chunkLength/2.0f;
454 else if (anchor == NS_STYLE_TEXT_ANCHOR_END)
455 x -= chunkLength;
457 // set position of each fragment in this chunk:
459 fragment = firstFragment;
460 while (fragment) {
462 float dx = 0.0f, dy = 0.0f;
464 nsCOMPtr<nsIDOMSVGLengthList> list = fragment->GetDx();
465 GetSingleValue(fragment, list, &dx);
468 nsCOMPtr<nsIDOMSVGLengthList> list = fragment->GetDy();
469 GetSingleValue(fragment, list, &dy);
472 float baseline_offset =
473 fragment->GetBaselineOffset(baseline, aForceGlobalTransform);
474 fragment->SetGlyphPosition(x + dx, y + dy - baseline_offset);
476 x += dx + fragment->GetAdvance(aForceGlobalTransform);
477 y += dy;
478 fragment = fragment->GetNextGlyphFragment();
479 if (fragment && fragment->IsAbsolutelyPositioned())
480 break;
482 firstFragment = fragment;