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
4 : *
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/
9 : *
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.
14 : *
15 : * The Original Code is the Mozilla SVG project.
16 : *
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.
21 : *
22 : * Contributor(s):
23 : * Alex Fritze <alex.fritze@crocodile-clips.com> (original author)
24 : *
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.
36 : *
37 : * ***** END LICENSE BLOCK ***** */
38 :
39 : #include "nsIDOMSVGTextElement.h"
40 : #include "nsSVGTextFrame.h"
41 : #include "SVGLengthList.h"
42 : #include "nsIDOMSVGLength.h"
43 : #include "nsIDOMSVGAnimatedNumber.h"
44 : #include "nsISVGGlyphFragmentNode.h"
45 : #include "nsSVGGlyphFrame.h"
46 : #include "nsSVGOuterSVGFrame.h"
47 : #include "nsIDOMSVGRect.h"
48 : #include "nsSVGRect.h"
49 : #include "nsGkAtoms.h"
50 : #include "nsSVGTextPathFrame.h"
51 : #include "nsSVGPathElement.h"
52 : #include "nsSVGUtils.h"
53 : #include "nsSVGGraphicElement.h"
54 :
55 : using namespace mozilla;
56 :
57 : //----------------------------------------------------------------------
58 : // Implementation
59 :
60 : nsIFrame*
61 0 : NS_NewSVGTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
62 : {
63 0 : return new (aPresShell) nsSVGTextFrame(aContext);
64 : }
65 :
66 0 : NS_IMPL_FRAMEARENA_HELPERS(nsSVGTextFrame)
67 :
68 : //----------------------------------------------------------------------
69 : // nsIFrame methods
70 : #ifdef DEBUG
71 : NS_IMETHODIMP
72 0 : nsSVGTextFrame::Init(nsIContent* aContent,
73 : nsIFrame* aParent,
74 : nsIFrame* aPrevInFlow)
75 : {
76 0 : nsCOMPtr<nsIDOMSVGTextElement> text = do_QueryInterface(aContent);
77 0 : NS_ASSERTION(text, "Content is not an SVG text");
78 :
79 0 : return nsSVGTextFrameBase::Init(aContent, aParent, aPrevInFlow);
80 : }
81 : #endif /* DEBUG */
82 :
83 : NS_IMETHODIMP
84 0 : nsSVGTextFrame::AttributeChanged(PRInt32 aNameSpaceID,
85 : nsIAtom* aAttribute,
86 : PRInt32 aModType)
87 : {
88 0 : if (aNameSpaceID != kNameSpaceID_None)
89 0 : return NS_OK;
90 :
91 0 : if (aAttribute == nsGkAtoms::transform) {
92 :
93 0 : NotifySVGChanged(TRANSFORM_CHANGED);
94 :
95 0 : } else if (aAttribute == nsGkAtoms::x ||
96 : aAttribute == nsGkAtoms::y ||
97 : aAttribute == nsGkAtoms::dx ||
98 : aAttribute == nsGkAtoms::dy ||
99 : aAttribute == nsGkAtoms::rotate) {
100 0 : NotifyGlyphMetricsChange();
101 : }
102 :
103 0 : return NS_OK;
104 : }
105 :
106 : nsIAtom *
107 0 : nsSVGTextFrame::GetType() const
108 : {
109 0 : return nsGkAtoms::svgTextFrame;
110 : }
111 :
112 : //----------------------------------------------------------------------
113 : // nsSVGTextContainerFrame
114 : PRUint32
115 0 : nsSVGTextFrame::GetNumberOfChars()
116 : {
117 0 : UpdateGlyphPositioning(false);
118 :
119 0 : return nsSVGTextFrameBase::GetNumberOfChars();
120 : }
121 :
122 : float
123 0 : nsSVGTextFrame::GetComputedTextLength()
124 : {
125 0 : UpdateGlyphPositioning(false);
126 :
127 0 : return nsSVGTextFrameBase::GetComputedTextLength();
128 : }
129 :
130 : float
131 0 : nsSVGTextFrame::GetSubStringLength(PRUint32 charnum, PRUint32 nchars)
132 : {
133 0 : UpdateGlyphPositioning(false);
134 :
135 0 : return nsSVGTextFrameBase::GetSubStringLength(charnum, nchars);
136 : }
137 :
138 : PRInt32
139 0 : nsSVGTextFrame::GetCharNumAtPosition(nsIDOMSVGPoint *point)
140 : {
141 0 : UpdateGlyphPositioning(false);
142 :
143 0 : return nsSVGTextFrameBase::GetCharNumAtPosition(point);
144 : }
145 :
146 : NS_IMETHODIMP
147 0 : nsSVGTextFrame::GetStartPositionOfChar(PRUint32 charnum, nsIDOMSVGPoint **_retval)
148 : {
149 0 : UpdateGlyphPositioning(false);
150 :
151 0 : return nsSVGTextFrameBase::GetStartPositionOfChar(charnum, _retval);
152 : }
153 :
154 : NS_IMETHODIMP
155 0 : nsSVGTextFrame::GetEndPositionOfChar(PRUint32 charnum, nsIDOMSVGPoint **_retval)
156 : {
157 0 : UpdateGlyphPositioning(false);
158 :
159 0 : return nsSVGTextFrameBase::GetEndPositionOfChar(charnum, _retval);
160 : }
161 :
162 : NS_IMETHODIMP
163 0 : nsSVGTextFrame::GetExtentOfChar(PRUint32 charnum, nsIDOMSVGRect **_retval)
164 : {
165 0 : UpdateGlyphPositioning(false);
166 :
167 0 : return nsSVGTextFrameBase::GetExtentOfChar(charnum, _retval);
168 : }
169 :
170 : NS_IMETHODIMP
171 0 : nsSVGTextFrame::GetRotationOfChar(PRUint32 charnum, float *_retval)
172 : {
173 0 : UpdateGlyphPositioning(false);
174 :
175 0 : return nsSVGTextFrameBase::GetRotationOfChar(charnum, _retval);
176 : }
177 :
178 : //----------------------------------------------------------------------
179 : // nsISVGChildFrame methods
180 :
181 : void
182 0 : nsSVGTextFrame::NotifySVGChanged(PRUint32 aFlags)
183 : {
184 0 : NS_ABORT_IF_FALSE(!(aFlags & DO_NOT_NOTIFY_RENDERING_OBSERVERS) ||
185 : (GetStateBits() & NS_STATE_SVG_NONDISPLAY_CHILD),
186 : "Must be NS_STATE_SVG_NONDISPLAY_CHILD!");
187 :
188 0 : NS_ABORT_IF_FALSE(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED),
189 : "Invalidation logic may need adjusting");
190 :
191 0 : bool updateGlyphMetrics = false;
192 :
193 0 : if (aFlags & COORD_CONTEXT_CHANGED) {
194 0 : updateGlyphMetrics = true;
195 : }
196 :
197 0 : if (aFlags & TRANSFORM_CHANGED) {
198 0 : if (mCanvasTM && mCanvasTM->IsSingular()) {
199 : // We won't have calculated the glyph positions correctly
200 0 : updateGlyphMetrics = true;
201 : }
202 : // make sure our cached transform matrix gets (lazily) updated
203 0 : mCanvasTM = nsnull;
204 : }
205 :
206 0 : nsSVGTextFrameBase::NotifySVGChanged(aFlags);
207 :
208 0 : if (updateGlyphMetrics) {
209 : // If we are positioned using percentage values we need to update our
210 : // position whenever our viewport's dimensions change.
211 :
212 : // XXX We could check here whether the text frame or any of its children
213 : // have any percentage co-ordinates and only update if they don't. This
214 : // may not be worth it as we might need to check each glyph
215 0 : NotifyGlyphMetricsChange();
216 : }
217 0 : }
218 :
219 : void
220 0 : nsSVGTextFrame::NotifyRedrawUnsuspended()
221 : {
222 0 : RemoveStateBits(NS_STATE_SVG_REDRAW_SUSPENDED);
223 :
224 0 : UpdateGlyphPositioning(false);
225 0 : nsSVGTextFrameBase::NotifyRedrawUnsuspended();
226 0 : }
227 :
228 : NS_IMETHODIMP
229 0 : nsSVGTextFrame::PaintSVG(nsRenderingContext* aContext,
230 : const nsIntRect *aDirtyRect)
231 : {
232 0 : UpdateGlyphPositioning(true);
233 :
234 0 : return nsSVGTextFrameBase::PaintSVG(aContext, aDirtyRect);
235 : }
236 :
237 : NS_IMETHODIMP_(nsIFrame*)
238 0 : nsSVGTextFrame::GetFrameForPoint(const nsPoint &aPoint)
239 : {
240 0 : UpdateGlyphPositioning(true);
241 :
242 0 : return nsSVGTextFrameBase::GetFrameForPoint(aPoint);
243 : }
244 :
245 : NS_IMETHODIMP
246 0 : nsSVGTextFrame::UpdateCoveredRegion()
247 : {
248 0 : UpdateGlyphPositioning(true);
249 :
250 0 : return nsSVGTextFrameBase::UpdateCoveredRegion();
251 : }
252 :
253 : NS_IMETHODIMP
254 0 : nsSVGTextFrame::InitialUpdate()
255 : {
256 0 : nsresult rv = nsSVGTextFrameBase::InitialUpdate();
257 :
258 0 : UpdateGlyphPositioning(false);
259 :
260 0 : return rv;
261 : }
262 :
263 : gfxRect
264 0 : nsSVGTextFrame::GetBBoxContribution(const gfxMatrix &aToBBoxUserspace,
265 : PRUint32 aFlags)
266 : {
267 0 : UpdateGlyphPositioning(true);
268 :
269 0 : return nsSVGTextFrameBase::GetBBoxContribution(aToBBoxUserspace, aFlags);
270 : }
271 :
272 : //----------------------------------------------------------------------
273 : // nsSVGContainerFrame methods:
274 :
275 : gfxMatrix
276 0 : nsSVGTextFrame::GetCanvasTM()
277 : {
278 0 : if (!mCanvasTM) {
279 0 : NS_ASSERTION(mParent, "null parent");
280 :
281 0 : nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(mParent);
282 0 : nsSVGGraphicElement *content = static_cast<nsSVGGraphicElement*>(mContent);
283 :
284 0 : gfxMatrix tm = content->PrependLocalTransformsTo(parent->GetCanvasTM());
285 :
286 0 : mCanvasTM = new gfxMatrix(tm);
287 : }
288 :
289 0 : return *mCanvasTM;
290 : }
291 :
292 : //----------------------------------------------------------------------
293 : //
294 :
295 : void
296 0 : nsSVGTextFrame::NotifyGlyphMetricsChange()
297 : {
298 0 : mPositioningDirty = true;
299 0 : UpdateGlyphPositioning(false);
300 0 : }
301 :
302 : void
303 0 : nsSVGTextFrame::SetWhitespaceHandling(nsSVGGlyphFrame *aFrame)
304 : {
305 0 : SetWhitespaceCompression();
306 :
307 0 : nsSVGGlyphFrame* firstFrame = aFrame;
308 0 : bool trimLeadingWhitespace = true;
309 0 : nsSVGGlyphFrame* lastNonWhitespaceFrame = aFrame;
310 :
311 : // If the previous frame ended with whitespace
312 : // then display of leading whitespace should be suppressed
313 : // when we are compressing whitespace.
314 0 : while (aFrame) {
315 0 : if (!aFrame->IsAllWhitespace()) {
316 0 : lastNonWhitespaceFrame = aFrame;
317 : }
318 :
319 0 : aFrame->SetTrimLeadingWhitespace(trimLeadingWhitespace);
320 0 : trimLeadingWhitespace = aFrame->EndsWithWhitespace();
321 :
322 0 : aFrame = aFrame->GetNextGlyphFrame();
323 : }
324 :
325 : // When there is only whitespace left we need to trim off
326 : // the end of the last frame that isn't entirely whitespace.
327 : // Making sure that we reset earlier frames as they may once
328 : // have been the last non-whitespace frame.
329 0 : aFrame = firstFrame;
330 0 : while (aFrame != lastNonWhitespaceFrame) {
331 0 : aFrame->SetTrimTrailingWhitespace(false);
332 0 : aFrame = aFrame->GetNextGlyphFrame();
333 : }
334 :
335 : // We're at the last non-whitespace frame so trim off the end
336 : // and make sure we set one of the trim bits so that any
337 : // further whitespace is compressed to nothing
338 0 : while (aFrame) {
339 0 : aFrame->SetTrimTrailingWhitespace(true);
340 0 : aFrame = aFrame->GetNextGlyphFrame();
341 : }
342 0 : }
343 :
344 : void
345 0 : nsSVGTextFrame::UpdateGlyphPositioning(bool aForceGlobalTransform)
346 : {
347 0 : if ((GetStateBits() & NS_STATE_SVG_REDRAW_SUSPENDED) || !mPositioningDirty)
348 0 : return;
349 :
350 0 : mPositioningDirty = false;
351 :
352 0 : nsISVGGlyphFragmentNode* node = GetFirstGlyphFragmentChildNode();
353 0 : if (!node)
354 0 : return;
355 :
356 : nsSVGGlyphFrame *frame, *firstFrame;
357 :
358 0 : firstFrame = node->GetFirstGlyphFrame();
359 0 : if (!firstFrame) {
360 0 : return;
361 : }
362 :
363 0 : SetWhitespaceHandling(firstFrame);
364 :
365 0 : BuildPositionList(0, 0);
366 :
367 0 : gfxPoint ctp(0.0, 0.0);
368 :
369 : // loop over chunks
370 0 : while (firstFrame) {
371 0 : nsSVGTextPathFrame *textPath = firstFrame->FindTextPathParent();
372 :
373 0 : nsTArray<float> effectiveXList, effectiveYList;
374 0 : firstFrame->GetEffectiveXY(firstFrame->GetNumberOfChars(),
375 0 : effectiveXList, effectiveYList);
376 0 : if (!effectiveXList.IsEmpty()) ctp.x = effectiveXList[0];
377 0 : if (!textPath && !effectiveYList.IsEmpty()) ctp.y = effectiveYList[0];
378 :
379 : // check for startOffset on textPath
380 0 : if (textPath) {
381 0 : if (!textPath->GetPathFrame()) {
382 : // invalid text path, give up
383 : return;
384 : }
385 0 : ctp.x = textPath->GetStartOffset();
386 : }
387 :
388 : // determine x offset based on text_anchor:
389 :
390 0 : PRUint8 anchor = firstFrame->GetTextAnchor();
391 :
392 : /**
393 : * XXXsmontagu: The SVG spec is very vague as to how 'text-anchor'
394 : * interacts with bidirectional text. It says:
395 : *
396 : * "For scripts that are inherently right to left such as Hebrew and
397 : * Arabic [text-anchor: start] is equivalent to right alignment."
398 : * and
399 : * "For scripts that are inherently right to left such as Hebrew and
400 : * Arabic, [text-anchor: end] is equivalent to left alignment.
401 : *
402 : * It's not clear how this should be implemented in terms of defined
403 : * properties, i.e. how one should determine that a particular element
404 : * contains a script that is inherently right to left.
405 : *
406 : * The code below follows http://www.w3.org/TR/SVGTiny12/text.html#TextAnchorProperty
407 : * and swaps the values of text-anchor: end and text-anchor: start
408 : * whenever the 'direction' property is rtl.
409 : *
410 : * This is probably the "right" thing to do, but other browsers don't do it,
411 : * so I am leaving it inside #if 0 for now for interoperability.
412 : *
413 : * See also XXXsmontagu comments in nsSVGGlyphFrame::EnsureTextRun
414 : */
415 : #if 0
416 : if (GetStyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL) {
417 : if (anchor == NS_STYLE_TEXT_ANCHOR_END) {
418 : anchor = NS_STYLE_TEXT_ANCHOR_START;
419 : } else if (anchor == NS_STYLE_TEXT_ANCHOR_START) {
420 : anchor = NS_STYLE_TEXT_ANCHOR_END;
421 : }
422 : }
423 : #endif
424 :
425 0 : float chunkLength = 0.0f;
426 0 : if (anchor != NS_STYLE_TEXT_ANCHOR_START) {
427 : // need to get the total chunk length
428 :
429 0 : frame = firstFrame;
430 0 : while (frame) {
431 0 : chunkLength += frame->GetAdvance(aForceGlobalTransform);
432 0 : frame = frame->GetNextGlyphFrame();
433 0 : if (frame && frame->IsAbsolutelyPositioned())
434 0 : break;
435 : }
436 : }
437 :
438 0 : if (anchor == NS_STYLE_TEXT_ANCHOR_MIDDLE)
439 0 : ctp.x -= chunkLength/2.0f;
440 0 : else if (anchor == NS_STYLE_TEXT_ANCHOR_END)
441 0 : ctp.x -= chunkLength;
442 :
443 : // set position of each frame in this chunk:
444 :
445 0 : frame = firstFrame;
446 0 : while (frame) {
447 :
448 0 : frame->SetGlyphPosition(&ctp, aForceGlobalTransform);
449 0 : frame = frame->GetNextGlyphFrame();
450 0 : if (frame && frame->IsAbsolutelyPositioned())
451 0 : break;
452 : }
453 0 : firstFrame = frame;
454 : }
455 0 : nsSVGUtils::UpdateGraphic(this);
456 : }
|