|
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 // Main header first: |
|
7 #include "SVGTextFrame.h" |
|
8 |
|
9 // Keep others in (case-insensitive) order: |
|
10 #include "DOMSVGPoint.h" |
|
11 #include "gfx2DGlue.h" |
|
12 #include "gfxFont.h" |
|
13 #include "gfxSkipChars.h" |
|
14 #include "gfxTypes.h" |
|
15 #include "LookAndFeel.h" |
|
16 #include "mozilla/gfx/2D.h" |
|
17 #include "nsAlgorithm.h" |
|
18 #include "nsBlockFrame.h" |
|
19 #include "nsCaret.h" |
|
20 #include "nsContentUtils.h" |
|
21 #include "nsGkAtoms.h" |
|
22 #include "nsIDOMSVGLength.h" |
|
23 #include "nsISelection.h" |
|
24 #include "nsQuickSort.h" |
|
25 #include "nsRenderingContext.h" |
|
26 #include "nsSVGEffects.h" |
|
27 #include "nsSVGOuterSVGFrame.h" |
|
28 #include "nsSVGPaintServerFrame.h" |
|
29 #include "mozilla/dom/SVGRect.h" |
|
30 #include "nsSVGIntegrationUtils.h" |
|
31 #include "nsSVGUtils.h" |
|
32 #include "nsTArray.h" |
|
33 #include "nsTextFrame.h" |
|
34 #include "nsTextNode.h" |
|
35 #include "SVGAnimatedNumberList.h" |
|
36 #include "SVGContentUtils.h" |
|
37 #include "SVGLengthList.h" |
|
38 #include "SVGNumberList.h" |
|
39 #include "SVGPathElement.h" |
|
40 #include "SVGTextPathElement.h" |
|
41 #include "nsLayoutUtils.h" |
|
42 #include <algorithm> |
|
43 #include <cmath> |
|
44 #include <limits> |
|
45 |
|
46 using namespace mozilla; |
|
47 using namespace mozilla::dom; |
|
48 using namespace mozilla::gfx; |
|
49 |
|
50 // ============================================================================ |
|
51 // Utility functions |
|
52 |
|
53 /** |
|
54 * Using the specified gfxSkipCharsIterator, converts an offset and length |
|
55 * in original char indexes to skipped char indexes. |
|
56 * |
|
57 * @param aIterator The gfxSkipCharsIterator to use for the conversion. |
|
58 * @param aOriginalOffset The original offset (input). |
|
59 * @param aOriginalLength The original length (input). |
|
60 * @param aSkippedOffset The skipped offset (output). |
|
61 * @param aSkippedLength The skipped length (output). |
|
62 */ |
|
63 static void |
|
64 ConvertOriginalToSkipped(gfxSkipCharsIterator& aIterator, |
|
65 uint32_t aOriginalOffset, uint32_t aOriginalLength, |
|
66 uint32_t& aSkippedOffset, uint32_t& aSkippedLength) |
|
67 { |
|
68 aSkippedOffset = aIterator.ConvertOriginalToSkipped(aOriginalOffset); |
|
69 aIterator.AdvanceOriginal(aOriginalLength); |
|
70 aSkippedLength = aIterator.GetSkippedOffset() - aSkippedOffset; |
|
71 } |
|
72 |
|
73 /** |
|
74 * Using the specified gfxSkipCharsIterator, converts an offset and length |
|
75 * in original char indexes to skipped char indexes in place. |
|
76 * |
|
77 * @param aIterator The gfxSkipCharsIterator to use for the conversion. |
|
78 * @param aOriginalOffset The offset to convert from original to skipped. |
|
79 * @param aOriginalLength The length to convert from original to skipped. |
|
80 */ |
|
81 static void |
|
82 ConvertOriginalToSkipped(gfxSkipCharsIterator& aIterator, |
|
83 uint32_t& aOffset, uint32_t& aLength) |
|
84 { |
|
85 ConvertOriginalToSkipped(aIterator, aOffset, aLength, aOffset, aLength); |
|
86 } |
|
87 |
|
88 /** |
|
89 * Converts an nsPoint from app units to user space units using the specified |
|
90 * nsPresContext and returns it as a gfxPoint. |
|
91 */ |
|
92 static gfxPoint |
|
93 AppUnitsToGfxUnits(const nsPoint& aPoint, const nsPresContext* aContext) |
|
94 { |
|
95 return gfxPoint(aContext->AppUnitsToGfxUnits(aPoint.x), |
|
96 aContext->AppUnitsToGfxUnits(aPoint.y)); |
|
97 } |
|
98 |
|
99 /** |
|
100 * Converts a gfxRect that is in app units to CSS pixels using the specified |
|
101 * nsPresContext and returns it as a gfxRect. |
|
102 */ |
|
103 static gfxRect |
|
104 AppUnitsToFloatCSSPixels(const gfxRect& aRect, const nsPresContext* aContext) |
|
105 { |
|
106 return gfxRect(aContext->AppUnitsToFloatCSSPixels(aRect.x), |
|
107 aContext->AppUnitsToFloatCSSPixels(aRect.y), |
|
108 aContext->AppUnitsToFloatCSSPixels(aRect.width), |
|
109 aContext->AppUnitsToFloatCSSPixels(aRect.height)); |
|
110 } |
|
111 |
|
112 /** |
|
113 * Scales a gfxRect around a given point. |
|
114 * |
|
115 * @param aRect The rectangle to scale. |
|
116 * @param aPoint The point around which to scale. |
|
117 * @param aScale The scale amount. |
|
118 */ |
|
119 static void |
|
120 ScaleAround(gfxRect& aRect, const gfxPoint& aPoint, double aScale) |
|
121 { |
|
122 aRect.x = aPoint.x - aScale * (aPoint.x - aRect.x); |
|
123 aRect.y = aPoint.y - aScale * (aPoint.y - aRect.y); |
|
124 aRect.width *= aScale; |
|
125 aRect.height *= aScale; |
|
126 } |
|
127 |
|
128 /** |
|
129 * Returns whether a gfxPoint lies within a gfxRect. |
|
130 */ |
|
131 static bool |
|
132 Inside(const gfxRect& aRect, const gfxPoint& aPoint) |
|
133 { |
|
134 return aPoint.x >= aRect.x && |
|
135 aPoint.x < aRect.XMost() && |
|
136 aPoint.y >= aRect.y && |
|
137 aPoint.y < aRect.YMost(); |
|
138 } |
|
139 |
|
140 /** |
|
141 * Gets the measured ascent and descent of the text in the given nsTextFrame |
|
142 * in app units. |
|
143 * |
|
144 * @param aFrame The text frame. |
|
145 * @param aAscent The ascent in app units (output). |
|
146 * @param aDescent The descent in app units (output). |
|
147 */ |
|
148 static void |
|
149 GetAscentAndDescentInAppUnits(nsTextFrame* aFrame, |
|
150 gfxFloat& aAscent, gfxFloat& aDescent) |
|
151 { |
|
152 gfxSkipCharsIterator it = aFrame->EnsureTextRun(nsTextFrame::eInflated); |
|
153 gfxTextRun* textRun = aFrame->GetTextRun(nsTextFrame::eInflated); |
|
154 |
|
155 uint32_t offset, length; |
|
156 ConvertOriginalToSkipped(it, |
|
157 aFrame->GetContentOffset(), |
|
158 aFrame->GetContentLength(), |
|
159 offset, length); |
|
160 |
|
161 gfxTextRun::Metrics metrics = |
|
162 textRun->MeasureText(offset, length, gfxFont::LOOSE_INK_EXTENTS, nullptr, |
|
163 nullptr); |
|
164 |
|
165 aAscent = metrics.mAscent; |
|
166 aDescent = metrics.mDescent; |
|
167 } |
|
168 |
|
169 /** |
|
170 * Updates an interval by intersecting it with another interval. |
|
171 * The intervals are specified using a start index and a length. |
|
172 */ |
|
173 static void |
|
174 IntersectInterval(uint32_t& aStart, uint32_t& aLength, |
|
175 uint32_t aStartOther, uint32_t aLengthOther) |
|
176 { |
|
177 uint32_t aEnd = aStart + aLength; |
|
178 uint32_t aEndOther = aStartOther + aLengthOther; |
|
179 |
|
180 if (aStartOther >= aEnd || aStart >= aEndOther) { |
|
181 aLength = 0; |
|
182 } else { |
|
183 if (aStartOther >= aStart) |
|
184 aStart = aStartOther; |
|
185 aLength = std::min(aEnd, aEndOther) - aStart; |
|
186 } |
|
187 } |
|
188 |
|
189 /** |
|
190 * Intersects an interval as IntersectInterval does but by taking |
|
191 * the offset and length of the other interval from a |
|
192 * nsTextFrame::TrimmedOffsets object. |
|
193 */ |
|
194 static void |
|
195 TrimOffsets(uint32_t& aStart, uint32_t& aLength, |
|
196 const nsTextFrame::TrimmedOffsets& aTrimmedOffsets) |
|
197 { |
|
198 IntersectInterval(aStart, aLength, |
|
199 aTrimmedOffsets.mStart, aTrimmedOffsets.mLength); |
|
200 } |
|
201 |
|
202 /** |
|
203 * Returns the closest ancestor-or-self node that is not an SVG <a> |
|
204 * element. |
|
205 */ |
|
206 static nsIContent* |
|
207 GetFirstNonAAncestor(nsIContent* aContent) |
|
208 { |
|
209 while (aContent && aContent->IsSVG(nsGkAtoms::a)) { |
|
210 aContent = aContent->GetParent(); |
|
211 } |
|
212 return aContent; |
|
213 } |
|
214 |
|
215 /** |
|
216 * Returns whether the given node is a text content element[1], taking into |
|
217 * account whether it has a valid parent. |
|
218 * |
|
219 * For example, in: |
|
220 * |
|
221 * <svg xmlns="http://www.w3.org/2000/svg"> |
|
222 * <text><a/><text/></text> |
|
223 * <tspan/> |
|
224 * </svg> |
|
225 * |
|
226 * true would be returned for the outer <text> element and the <a> element, |
|
227 * and false for the inner <text> element (since a <text> is not allowed |
|
228 * to be a child of another <text>) and the <tspan> element (because it |
|
229 * must be inside a <text> subtree). |
|
230 * |
|
231 * Note that we don't support the <tref> element yet and this function |
|
232 * returns false for it. |
|
233 * |
|
234 * [1] https://svgwg.org/svg2-draft/intro.html#TermTextContentElement |
|
235 */ |
|
236 static bool |
|
237 IsTextContentElement(nsIContent* aContent) |
|
238 { |
|
239 if (!aContent->IsSVG()) { |
|
240 return false; |
|
241 } |
|
242 |
|
243 if (aContent->Tag() == nsGkAtoms::text) { |
|
244 nsIContent* parent = GetFirstNonAAncestor(aContent->GetParent()); |
|
245 return !parent || !IsTextContentElement(parent); |
|
246 } |
|
247 |
|
248 if (aContent->Tag() == nsGkAtoms::textPath) { |
|
249 nsIContent* parent = GetFirstNonAAncestor(aContent->GetParent()); |
|
250 return parent && parent->IsSVG(nsGkAtoms::text); |
|
251 } |
|
252 |
|
253 if (aContent->Tag() == nsGkAtoms::a || |
|
254 aContent->Tag() == nsGkAtoms::tspan || |
|
255 aContent->Tag() == nsGkAtoms::altGlyph) { |
|
256 return true; |
|
257 } |
|
258 |
|
259 return false; |
|
260 } |
|
261 |
|
262 /** |
|
263 * Returns whether the specified frame is an nsTextFrame that has some text |
|
264 * content. |
|
265 */ |
|
266 static bool |
|
267 IsNonEmptyTextFrame(nsIFrame* aFrame) |
|
268 { |
|
269 nsTextFrame* textFrame = do_QueryFrame(aFrame); |
|
270 if (!textFrame) { |
|
271 return false; |
|
272 } |
|
273 |
|
274 return textFrame->GetContentLength() != 0; |
|
275 } |
|
276 |
|
277 /** |
|
278 * Takes an nsIFrame and if it is a text frame that has some text content, |
|
279 * returns it as an nsTextFrame and its corresponding nsTextNode. |
|
280 * |
|
281 * @param aFrame The frame to look at. |
|
282 * @param aTextFrame aFrame as an nsTextFrame (output). |
|
283 * @param aTextNode The nsTextNode content of aFrame (output). |
|
284 * @return true if aFrame is a non-empty text frame, false otherwise. |
|
285 */ |
|
286 static bool |
|
287 GetNonEmptyTextFrameAndNode(nsIFrame* aFrame, |
|
288 nsTextFrame*& aTextFrame, |
|
289 nsTextNode*& aTextNode) |
|
290 { |
|
291 nsTextFrame* text = do_QueryFrame(aFrame); |
|
292 if (!text) { |
|
293 return false; |
|
294 } |
|
295 |
|
296 nsIContent* content = text->GetContent(); |
|
297 NS_ASSERTION(content && content->IsNodeOfType(nsINode::eTEXT), |
|
298 "unexpected content type for nsTextFrame"); |
|
299 |
|
300 nsTextNode* node = static_cast<nsTextNode*>(content); |
|
301 if (node->TextLength() == 0) { |
|
302 return false; |
|
303 } |
|
304 |
|
305 aTextFrame = text; |
|
306 aTextNode = node; |
|
307 return true; |
|
308 } |
|
309 |
|
310 /** |
|
311 * Returns whether the specified atom is for one of the five |
|
312 * glyph positioning attributes that can appear on SVG text |
|
313 * elements -- x, y, dx, dy or rotate. |
|
314 */ |
|
315 static bool |
|
316 IsGlyphPositioningAttribute(nsIAtom* aAttribute) |
|
317 { |
|
318 return aAttribute == nsGkAtoms::x || |
|
319 aAttribute == nsGkAtoms::y || |
|
320 aAttribute == nsGkAtoms::dx || |
|
321 aAttribute == nsGkAtoms::dy || |
|
322 aAttribute == nsGkAtoms::rotate; |
|
323 } |
|
324 |
|
325 /** |
|
326 * Returns the position in app units of a given baseline (using an |
|
327 * SVG dominant-baseline property value) for a given nsTextFrame. |
|
328 * |
|
329 * @param aFrame The text frame to inspect. |
|
330 * @param aTextRun The text run of aFrame. |
|
331 * @param aDominantBaseline The dominant-baseline value to use. |
|
332 */ |
|
333 static nscoord |
|
334 GetBaselinePosition(nsTextFrame* aFrame, |
|
335 gfxTextRun* aTextRun, |
|
336 uint8_t aDominantBaseline) |
|
337 { |
|
338 switch (aDominantBaseline) { |
|
339 case NS_STYLE_DOMINANT_BASELINE_HANGING: |
|
340 case NS_STYLE_DOMINANT_BASELINE_TEXT_BEFORE_EDGE: |
|
341 return 0; |
|
342 case NS_STYLE_DOMINANT_BASELINE_USE_SCRIPT: |
|
343 case NS_STYLE_DOMINANT_BASELINE_NO_CHANGE: |
|
344 case NS_STYLE_DOMINANT_BASELINE_RESET_SIZE: |
|
345 // These three should not simply map to 'baseline', but we don't |
|
346 // support the complex baseline model that SVG 1.1 has and which |
|
347 // css3-linebox now defines. |
|
348 // (fall through) |
|
349 case NS_STYLE_DOMINANT_BASELINE_AUTO: |
|
350 case NS_STYLE_DOMINANT_BASELINE_ALPHABETIC: |
|
351 return aFrame->GetBaseline(); |
|
352 } |
|
353 |
|
354 gfxTextRun::Metrics metrics = |
|
355 aTextRun->MeasureText(0, aTextRun->GetLength(), gfxFont::LOOSE_INK_EXTENTS, |
|
356 nullptr, nullptr); |
|
357 |
|
358 switch (aDominantBaseline) { |
|
359 case NS_STYLE_DOMINANT_BASELINE_TEXT_AFTER_EDGE: |
|
360 case NS_STYLE_DOMINANT_BASELINE_IDEOGRAPHIC: |
|
361 return metrics.mAscent + metrics.mDescent; |
|
362 case NS_STYLE_DOMINANT_BASELINE_CENTRAL: |
|
363 case NS_STYLE_DOMINANT_BASELINE_MIDDLE: |
|
364 case NS_STYLE_DOMINANT_BASELINE_MATHEMATICAL: |
|
365 return (metrics.mAscent + metrics.mDescent) / 2.0; |
|
366 } |
|
367 |
|
368 NS_NOTREACHED("unexpected dominant-baseline value"); |
|
369 return aFrame->GetBaseline(); |
|
370 } |
|
371 |
|
372 /** |
|
373 * For a given text run, returns the number of skipped characters that comprise |
|
374 * the ligature group and/or cluster that includes the character represented |
|
375 * by the specified gfxSkipCharsIterator. |
|
376 * |
|
377 * @param aTextRun The text run to use for determining whether a given character |
|
378 * is part of a ligature or cluster. |
|
379 * @param aIterator The gfxSkipCharsIterator to use for the current position |
|
380 * in the text run. |
|
381 */ |
|
382 static uint32_t |
|
383 ClusterLength(gfxTextRun* aTextRun, const gfxSkipCharsIterator& aIterator) |
|
384 { |
|
385 uint32_t start = aIterator.GetSkippedOffset(); |
|
386 uint32_t end = start + 1; |
|
387 while (end < aTextRun->GetLength() && |
|
388 (!aTextRun->IsLigatureGroupStart(end) || |
|
389 !aTextRun->IsClusterStart(end))) { |
|
390 end++; |
|
391 } |
|
392 return end - start; |
|
393 } |
|
394 |
|
395 /** |
|
396 * Truncates an array to be at most the length of another array. |
|
397 * |
|
398 * @param aArrayToTruncate The array to truncate. |
|
399 * @param aReferenceArray The array whose length will be used to truncate |
|
400 * aArrayToTruncate to. |
|
401 */ |
|
402 template<typename T, typename U> |
|
403 static void |
|
404 TruncateTo(nsTArray<T>& aArrayToTruncate, const nsTArray<U>& aReferenceArray) |
|
405 { |
|
406 uint32_t length = aReferenceArray.Length(); |
|
407 if (aArrayToTruncate.Length() > length) { |
|
408 aArrayToTruncate.TruncateLength(length); |
|
409 } |
|
410 } |
|
411 |
|
412 /** |
|
413 * Asserts that the anonymous block child of the SVGTextFrame has been |
|
414 * reflowed (or does not exist). Returns null if the child has not been |
|
415 * reflowed, and the frame otherwise. |
|
416 * |
|
417 * We check whether the kid has been reflowed and not the frame itself |
|
418 * since we sometimes need to call this function during reflow, after the |
|
419 * kid has been reflowed but before we have cleared the dirty bits on the |
|
420 * frame itself. |
|
421 */ |
|
422 static SVGTextFrame* |
|
423 FrameIfAnonymousChildReflowed(SVGTextFrame* aFrame) |
|
424 { |
|
425 NS_PRECONDITION(aFrame, "aFrame must not be null"); |
|
426 nsIFrame* kid = aFrame->GetFirstPrincipalChild(); |
|
427 if (NS_SUBTREE_DIRTY(kid)) { |
|
428 MOZ_ASSERT(false, "should have already reflowed the anonymous block child"); |
|
429 return nullptr; |
|
430 } |
|
431 return aFrame; |
|
432 } |
|
433 |
|
434 static double |
|
435 GetContextScale(const gfxMatrix& aMatrix) |
|
436 { |
|
437 // The context scale is the ratio of the length of the transformed |
|
438 // diagonal vector (1,1) to the length of the untransformed diagonal |
|
439 // (which is sqrt(2)). |
|
440 gfxPoint p = aMatrix.Transform(gfxPoint(1, 1)) - |
|
441 aMatrix.Transform(gfxPoint(0, 0)); |
|
442 return SVGContentUtils::ComputeNormalizedHypotenuse(p.x, p.y); |
|
443 } |
|
444 |
|
445 // ============================================================================ |
|
446 // Utility classes |
|
447 |
|
448 namespace mozilla { |
|
449 |
|
450 // ---------------------------------------------------------------------------- |
|
451 // TextRenderedRun |
|
452 |
|
453 /** |
|
454 * A run of text within a single nsTextFrame whose glyphs can all be painted |
|
455 * with a single call to nsTextFrame::PaintText. A text rendered run can |
|
456 * be created for a sequence of two or more consecutive glyphs as long as: |
|
457 * |
|
458 * - Only the first glyph has (or none of the glyphs have) been positioned |
|
459 * with SVG text positioning attributes |
|
460 * - All of the glyphs have zero rotation |
|
461 * - The glyphs are not on a text path |
|
462 * - The glyphs correspond to content within the one nsTextFrame |
|
463 * |
|
464 * A TextRenderedRunIterator produces TextRenderedRuns required for painting a |
|
465 * whole SVGTextFrame. |
|
466 */ |
|
467 struct TextRenderedRun |
|
468 { |
|
469 /** |
|
470 * Constructs a TextRenderedRun that is uninitialized except for mFrame |
|
471 * being null. |
|
472 */ |
|
473 TextRenderedRun() |
|
474 : mFrame(nullptr) |
|
475 { |
|
476 } |
|
477 |
|
478 /** |
|
479 * Constructs a TextRenderedRun with all of the information required to |
|
480 * paint it. See the comments documenting the member variables below |
|
481 * for descriptions of the arguments. |
|
482 */ |
|
483 TextRenderedRun(nsTextFrame* aFrame, const gfxPoint& aPosition, |
|
484 float aLengthAdjustScaleFactor, double aRotate, |
|
485 float aFontSizeScaleFactor, nscoord aBaseline, |
|
486 uint32_t aTextFrameContentOffset, |
|
487 uint32_t aTextFrameContentLength, |
|
488 uint32_t aTextElementCharIndex) |
|
489 : mFrame(aFrame), |
|
490 mPosition(aPosition), |
|
491 mLengthAdjustScaleFactor(aLengthAdjustScaleFactor), |
|
492 mRotate(static_cast<float>(aRotate)), |
|
493 mFontSizeScaleFactor(aFontSizeScaleFactor), |
|
494 mBaseline(aBaseline), |
|
495 mTextFrameContentOffset(aTextFrameContentOffset), |
|
496 mTextFrameContentLength(aTextFrameContentLength), |
|
497 mTextElementCharIndex(aTextElementCharIndex) |
|
498 { |
|
499 } |
|
500 |
|
501 /** |
|
502 * Returns the text run for the text frame that this rendered run is part of. |
|
503 */ |
|
504 gfxTextRun* GetTextRun() const |
|
505 { |
|
506 mFrame->EnsureTextRun(nsTextFrame::eInflated); |
|
507 return mFrame->GetTextRun(nsTextFrame::eInflated); |
|
508 } |
|
509 |
|
510 /** |
|
511 * Returns whether this rendered run is RTL. |
|
512 */ |
|
513 bool IsRightToLeft() const |
|
514 { |
|
515 return GetTextRun()->IsRightToLeft(); |
|
516 } |
|
517 |
|
518 /** |
|
519 * Returns the transform that converts from a <text> element's user space into |
|
520 * the coordinate space that rendered runs can be painted directly in. |
|
521 * |
|
522 * The difference between this method and GetTransformFromRunUserSpaceToUserSpace |
|
523 * is that when calling in to nsTextFrame::PaintText, it will already take |
|
524 * into account any left clip edge (that is, it doesn't just apply a visual |
|
525 * clip to the rendered text, it shifts the glyphs over so that they are |
|
526 * painted with their left edge at the x coordinate passed in to it). |
|
527 * Thus we need to account for this in our transform. |
|
528 * |
|
529 * |
|
530 * Assume that we have <text x="100" y="100" rotate="0 0 1 0 0 1">abcdef</text>. |
|
531 * This would result in four text rendered runs: |
|
532 * |
|
533 * - one for "ab" |
|
534 * - one for "c" |
|
535 * - one for "de" |
|
536 * - one for "f" |
|
537 * |
|
538 * Assume now that we are painting the third TextRenderedRun. It will have |
|
539 * a left clip edge that is the sum of the advances of "abc", and it will |
|
540 * have a right clip edge that is the advance of "f". In |
|
541 * SVGTextFrame::PaintSVG(), we pass in nsPoint() (i.e., the origin) |
|
542 * as the point at which to paint the text frame, and we pass in the |
|
543 * clip edge values. The nsTextFrame will paint the substring of its |
|
544 * text such that the top-left corner of the "d"'s glyph cell will be at |
|
545 * (0, 0) in the current coordinate system. |
|
546 * |
|
547 * Thus, GetTransformFromUserSpaceForPainting must return a transform from |
|
548 * whatever user space the <text> element is in to a coordinate space in |
|
549 * device pixels (as that's what nsTextFrame works in) where the origin is at |
|
550 * the same position as our user space mPositions[i].mPosition value for |
|
551 * the "d" glyph, which will be (100 + userSpaceAdvance("abc"), 100). |
|
552 * The translation required to do this (ignoring the scale to get from |
|
553 * user space to device pixels, and ignoring the |
|
554 * (100 + userSpaceAdvance("abc"), 100) translation) is: |
|
555 * |
|
556 * (-leftEdge, -baseline) |
|
557 * |
|
558 * where baseline is the distance between the baseline of the text and the top |
|
559 * edge of the nsTextFrame. We translate by -leftEdge horizontally because |
|
560 * the nsTextFrame will already shift the glyphs over by that amount and start |
|
561 * painting glyphs at x = 0. We translate by -baseline vertically so that |
|
562 * painting the top edges of the glyphs at y = 0 will result in their |
|
563 * baselines being at our desired y position. |
|
564 * |
|
565 * |
|
566 * Now for an example with RTL text. Assume our content is now |
|
567 * <text x="100" y="100" rotate="0 0 1 0 0 1">WERBEH</text>. We'd have |
|
568 * the following text rendered runs: |
|
569 * |
|
570 * - one for "EH" |
|
571 * - one for "B" |
|
572 * - one for "ER" |
|
573 * - one for "W" |
|
574 * |
|
575 * Again, we are painting the third TextRenderedRun. The left clip edge |
|
576 * is the advance of the "W" and the right clip edge is the sum of the |
|
577 * advances of "BEH". Our translation to get the rendered "ER" glyphs |
|
578 * in the right place this time is: |
|
579 * |
|
580 * (-frameWidth + rightEdge, -baseline) |
|
581 * |
|
582 * which is equivalent to: |
|
583 * |
|
584 * (-(leftEdge + advance("ER")), -baseline) |
|
585 * |
|
586 * The reason we have to shift left additionally by the width of the run |
|
587 * of glyphs we are painting is that although the nsTextFrame is RTL, |
|
588 * we still supply the top-left corner to paint the frame at when calling |
|
589 * nsTextFrame::PaintText, even though our user space positions for each |
|
590 * glyph in mPositions specifies the origin of each glyph, which for RTL |
|
591 * glyphs is at the right edge of the glyph cell. |
|
592 * |
|
593 * |
|
594 * For any other use of an nsTextFrame in the context of a particular run |
|
595 * (such as hit testing, or getting its rectangle), |
|
596 * GetTransformFromRunUserSpaceToUserSpace should be used. |
|
597 * |
|
598 * @param aContext The context to use for unit conversions. |
|
599 * @param aItem The nsCharClipDisplayItem that holds the amount of clipping |
|
600 * from the left and right edges of the text frame for this rendered run. |
|
601 * An appropriate nsCharClipDisplayItem can be obtained by constructing an |
|
602 * SVGCharClipDisplayItem for the TextRenderedRun. |
|
603 */ |
|
604 gfxMatrix GetTransformFromUserSpaceForPainting( |
|
605 nsPresContext* aContext, |
|
606 const nsCharClipDisplayItem& aItem) const; |
|
607 |
|
608 /** |
|
609 * Returns the transform that converts from "run user space" to a <text> |
|
610 * element's user space. Run user space is a coordinate system that has the |
|
611 * same size as the <text>'s user space but rotated and translated such that |
|
612 * (0,0) is the top-left of the rectangle that bounds the text. |
|
613 * |
|
614 * @param aContext The context to use for unit conversions. |
|
615 */ |
|
616 gfxMatrix GetTransformFromRunUserSpaceToUserSpace(nsPresContext* aContext) const; |
|
617 |
|
618 /** |
|
619 * Returns the transform that converts from "run user space" to float pixels |
|
620 * relative to the nsTextFrame that this rendered run is a part of. |
|
621 * |
|
622 * @param aContext The context to use for unit conversions. |
|
623 */ |
|
624 gfxMatrix GetTransformFromRunUserSpaceToFrameUserSpace(nsPresContext* aContext) const; |
|
625 |
|
626 /** |
|
627 * Flag values used for the aFlags arguments of GetRunUserSpaceRect, |
|
628 * GetFrameUserSpaceRect and GetUserSpaceRect. |
|
629 */ |
|
630 enum { |
|
631 // Includes the fill geometry of the text in the returned rectangle. |
|
632 eIncludeFill = 1, |
|
633 // Includes the stroke geometry of the text in the returned rectangle. |
|
634 eIncludeStroke = 2, |
|
635 // Includes any text shadow in the returned rectangle. |
|
636 eIncludeTextShadow = 4, |
|
637 // Don't include any horizontal glyph overflow in the returned rectangle. |
|
638 eNoHorizontalOverflow = 8 |
|
639 }; |
|
640 |
|
641 /** |
|
642 * Returns a rectangle that bounds the fill and/or stroke of the rendered run |
|
643 * in run user space. |
|
644 * |
|
645 * @param aContext The context to use for unit conversions. |
|
646 * @param aFlags A combination of the flags above (eIncludeFill and |
|
647 * eIncludeStroke) indicating what parts of the text to include in |
|
648 * the rectangle. |
|
649 */ |
|
650 SVGBBox GetRunUserSpaceRect(nsPresContext* aContext, uint32_t aFlags) const; |
|
651 |
|
652 /** |
|
653 * Returns a rectangle that covers the fill and/or stroke of the rendered run |
|
654 * in "frame user space". |
|
655 * |
|
656 * Frame user space is a coordinate space of the same scale as the <text> |
|
657 * element's user space, but with its rotation set to the rotation of |
|
658 * the glyphs within this rendered run and its origin set to the position |
|
659 * such that placing the nsTextFrame there would result in the glyphs in |
|
660 * this rendered run being at their correct positions. |
|
661 * |
|
662 * For example, say we have <text x="100 150" y="100">ab</text>. Assume |
|
663 * the advance of both the "a" and the "b" is 12 user units, and the |
|
664 * ascent of the text is 8 user units and its descent is 6 user units, |
|
665 * and that we are not measuing the stroke of the text, so that we stay |
|
666 * entirely within the glyph cells. |
|
667 * |
|
668 * There will be two text rendered runs, one for "a" and one for "b". |
|
669 * |
|
670 * The frame user space for the "a" run will have its origin at |
|
671 * (100, 100 - 8) in the <text> element's user space and will have its |
|
672 * axes aligned with the user space (since there is no rotate="" or |
|
673 * text path involve) and with its scale the same as the user space. |
|
674 * The rect returned by this method will be (0, 0, 12, 14), since the "a" |
|
675 * glyph is right at the left of the nsTextFrame. |
|
676 * |
|
677 * The frame user space for the "b" run will have its origin at |
|
678 * (150 - 12, 100 - 8), and scale/rotation the same as above. The rect |
|
679 * returned by this method will be (12, 0, 12, 14), since we are |
|
680 * advance("a") horizontally in to the text frame. |
|
681 * |
|
682 * @param aContext The context to use for unit conversions. |
|
683 * @param aFlags A combination of the flags above (eIncludeFill and |
|
684 * eIncludeStroke) indicating what parts of the text to include in |
|
685 * the rectangle. |
|
686 */ |
|
687 SVGBBox GetFrameUserSpaceRect(nsPresContext* aContext, uint32_t aFlags) const; |
|
688 |
|
689 /** |
|
690 * Returns a rectangle that covers the fill and/or stroke of the rendered run |
|
691 * in the <text> element's user space. |
|
692 * |
|
693 * @param aContext The context to use for unit conversions. |
|
694 * @param aFlags A combination of the flags above indicating what parts of the |
|
695 * text to include in the rectangle. |
|
696 * @param aAdditionalTransform An additional transform to apply to the |
|
697 * frame user space rectangle before its bounds are transformed into |
|
698 * user space. |
|
699 */ |
|
700 SVGBBox GetUserSpaceRect(nsPresContext* aContext, uint32_t aFlags, |
|
701 const gfxMatrix* aAdditionalTransform = nullptr) const; |
|
702 |
|
703 /** |
|
704 * Gets the app unit amounts to clip from the left and right edges of |
|
705 * the nsTextFrame in order to paint just this rendered run. |
|
706 * |
|
707 * Note that if clip edge amounts land in the middle of a glyph, the |
|
708 * glyph won't be painted at all. The clip edges are thus more of |
|
709 * a selection mechanism for which glyphs will be painted, rather |
|
710 * than a geometric clip. |
|
711 */ |
|
712 void GetClipEdges(nscoord& aLeftEdge, nscoord& aRightEdge) const; |
|
713 |
|
714 /** |
|
715 * Returns the advance width of the whole rendered run. |
|
716 */ |
|
717 nscoord GetAdvanceWidth() const; |
|
718 |
|
719 /** |
|
720 * Returns the index of the character into this rendered run whose |
|
721 * glyph cell contains the given point, or -1 if there is no such |
|
722 * character. This does not hit test against any overflow. |
|
723 * |
|
724 * @param aContext The context to use for unit conversions. |
|
725 * @param aPoint The point in the user space of the <text> element. |
|
726 */ |
|
727 int32_t GetCharNumAtPosition(nsPresContext* aContext, |
|
728 const gfxPoint& aPoint) const; |
|
729 |
|
730 /** |
|
731 * The text frame that this rendered run lies within. |
|
732 */ |
|
733 nsTextFrame* mFrame; |
|
734 |
|
735 /** |
|
736 * The point in user space that the text is positioned at. |
|
737 * |
|
738 * The x coordinate is the left edge of a LTR run of text or the right edge of |
|
739 * an RTL run. The y coordinate is the baseline of the text. |
|
740 */ |
|
741 gfxPoint mPosition; |
|
742 |
|
743 /** |
|
744 * The horizontal scale factor to apply when painting glyphs to take |
|
745 * into account textLength="". |
|
746 */ |
|
747 float mLengthAdjustScaleFactor; |
|
748 |
|
749 /** |
|
750 * The rotation in radians in the user coordinate system that the text has. |
|
751 */ |
|
752 float mRotate; |
|
753 |
|
754 /** |
|
755 * The scale factor that was used to transform the text run's original font |
|
756 * size into a sane range for painting and measurement. |
|
757 */ |
|
758 double mFontSizeScaleFactor; |
|
759 |
|
760 /** |
|
761 * The baseline in app units of this text run. The measurement is from the |
|
762 * top of the text frame. |
|
763 */ |
|
764 nscoord mBaseline; |
|
765 |
|
766 /** |
|
767 * The offset and length in mFrame's content nsTextNode that corresponds to |
|
768 * this text rendered run. These are original char indexes. |
|
769 */ |
|
770 uint32_t mTextFrameContentOffset; |
|
771 uint32_t mTextFrameContentLength; |
|
772 |
|
773 /** |
|
774 * The character index in the whole SVG <text> element that this text rendered |
|
775 * run begins at. |
|
776 */ |
|
777 uint32_t mTextElementCharIndex; |
|
778 }; |
|
779 |
|
780 gfxMatrix |
|
781 TextRenderedRun::GetTransformFromUserSpaceForPainting( |
|
782 nsPresContext* aContext, |
|
783 const nsCharClipDisplayItem& aItem) const |
|
784 { |
|
785 // We transform to device pixels positioned such that painting the text frame |
|
786 // at (0,0) with aItem will result in the text being in the right place. |
|
787 |
|
788 gfxMatrix m; |
|
789 if (!mFrame) { |
|
790 return m; |
|
791 } |
|
792 |
|
793 float cssPxPerDevPx = aContext-> |
|
794 AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel()); |
|
795 |
|
796 // Glyph position in user space. |
|
797 m.Translate(mPosition / cssPxPerDevPx); |
|
798 |
|
799 // Take into account any font size scaling and scaling due to textLength="". |
|
800 m.Scale(1.0 / mFontSizeScaleFactor, 1.0 / mFontSizeScaleFactor); |
|
801 |
|
802 // Rotation due to rotate="" or a <textPath>. |
|
803 m.Rotate(mRotate); |
|
804 |
|
805 m.Scale(mLengthAdjustScaleFactor, 1.0); |
|
806 |
|
807 // Translation to get the text frame in the right place. |
|
808 nsPoint t(IsRightToLeft() ? |
|
809 -mFrame->GetRect().width + aItem.mRightEdge : |
|
810 -aItem.mLeftEdge, |
|
811 -mBaseline); |
|
812 m.Translate(AppUnitsToGfxUnits(t, aContext)); |
|
813 |
|
814 return m; |
|
815 } |
|
816 |
|
817 gfxMatrix |
|
818 TextRenderedRun::GetTransformFromRunUserSpaceToUserSpace( |
|
819 nsPresContext* aContext) const |
|
820 { |
|
821 gfxMatrix m; |
|
822 if (!mFrame) { |
|
823 return m; |
|
824 } |
|
825 |
|
826 float cssPxPerDevPx = aContext-> |
|
827 AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel()); |
|
828 |
|
829 nscoord left, right; |
|
830 GetClipEdges(left, right); |
|
831 |
|
832 // Glyph position in user space. |
|
833 m.Translate(mPosition); |
|
834 |
|
835 // Rotation due to rotate="" or a <textPath>. |
|
836 m.Rotate(mRotate); |
|
837 |
|
838 // Scale due to textLength="". |
|
839 m.Scale(mLengthAdjustScaleFactor, 1.0); |
|
840 |
|
841 // Translation to get the text frame in the right place. |
|
842 nsPoint t(IsRightToLeft() ? |
|
843 -mFrame->GetRect().width + left + right : |
|
844 0, |
|
845 -mBaseline); |
|
846 m.Translate(AppUnitsToGfxUnits(t, aContext) * |
|
847 cssPxPerDevPx / mFontSizeScaleFactor); |
|
848 |
|
849 return m; |
|
850 } |
|
851 |
|
852 gfxMatrix |
|
853 TextRenderedRun::GetTransformFromRunUserSpaceToFrameUserSpace( |
|
854 nsPresContext* aContext) const |
|
855 { |
|
856 gfxMatrix m; |
|
857 if (!mFrame) { |
|
858 return m; |
|
859 } |
|
860 |
|
861 nscoord left, right; |
|
862 GetClipEdges(left, right); |
|
863 |
|
864 // Translate by the horizontal distance into the text frame this |
|
865 // rendered run is. |
|
866 return m.Translate(gfxPoint(gfxFloat(left) / aContext->AppUnitsPerCSSPixel(), |
|
867 0)); |
|
868 } |
|
869 |
|
870 SVGBBox |
|
871 TextRenderedRun::GetRunUserSpaceRect(nsPresContext* aContext, |
|
872 uint32_t aFlags) const |
|
873 { |
|
874 SVGBBox r; |
|
875 if (!mFrame) { |
|
876 return r; |
|
877 } |
|
878 |
|
879 // Determine the amount of overflow above and below the frame's mRect. |
|
880 // |
|
881 // We need to call GetVisualOverflowRectRelativeToSelf because this includes |
|
882 // overflowing decorations, which the MeasureText call below does not. We |
|
883 // assume here the decorations only overflow above and below the frame, never |
|
884 // horizontally. |
|
885 nsRect self = mFrame->GetVisualOverflowRectRelativeToSelf(); |
|
886 nsRect rect = mFrame->GetRect(); |
|
887 nscoord above = -self.y; |
|
888 nscoord below = self.YMost() - rect.height; |
|
889 |
|
890 gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated); |
|
891 gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated); |
|
892 |
|
893 // Get the content range for this rendered run. |
|
894 uint32_t offset, length; |
|
895 ConvertOriginalToSkipped(it, mTextFrameContentOffset, mTextFrameContentLength, |
|
896 offset, length); |
|
897 |
|
898 // Measure that range. |
|
899 gfxTextRun::Metrics metrics = |
|
900 textRun->MeasureText(offset, length, gfxFont::LOOSE_INK_EXTENTS, |
|
901 nullptr, nullptr); |
|
902 |
|
903 // Determine the rectangle that covers the rendered run's fill, |
|
904 // taking into account the measured vertical overflow due to |
|
905 // decorations. |
|
906 nscoord baseline = metrics.mBoundingBox.y + metrics.mAscent; |
|
907 gfxFloat x, width; |
|
908 if (aFlags & eNoHorizontalOverflow) { |
|
909 x = 0.0; |
|
910 width = textRun->GetAdvanceWidth(offset, length, nullptr); |
|
911 } else { |
|
912 x = metrics.mBoundingBox.x; |
|
913 width = metrics.mBoundingBox.width; |
|
914 } |
|
915 nsRect fillInAppUnits(x, baseline - above, |
|
916 width, metrics.mBoundingBox.height + above + below); |
|
917 |
|
918 // Account for text-shadow. |
|
919 if (aFlags & eIncludeTextShadow) { |
|
920 fillInAppUnits = |
|
921 nsLayoutUtils::GetTextShadowRectsUnion(fillInAppUnits, mFrame); |
|
922 } |
|
923 |
|
924 // Convert the app units rectangle to user units. |
|
925 gfxRect fill = AppUnitsToFloatCSSPixels(gfxRect(fillInAppUnits.x, |
|
926 fillInAppUnits.y, |
|
927 fillInAppUnits.width, |
|
928 fillInAppUnits.height), |
|
929 aContext); |
|
930 |
|
931 // Scale the rectangle up due to any mFontSizeScaleFactor. We scale |
|
932 // it around the text's origin. |
|
933 ScaleAround(fill, |
|
934 gfxPoint(0.0, aContext->AppUnitsToFloatCSSPixels(baseline)), |
|
935 1.0 / mFontSizeScaleFactor); |
|
936 |
|
937 // Include the fill if requested. |
|
938 if (aFlags & eIncludeFill) { |
|
939 r = fill; |
|
940 } |
|
941 |
|
942 // Include the stroke if requested. |
|
943 if ((aFlags & eIncludeStroke) && |
|
944 nsSVGUtils::GetStrokeWidth(mFrame) > 0) { |
|
945 r.UnionEdges(nsSVGUtils::PathExtentsToMaxStrokeExtents(fill, mFrame, |
|
946 gfxMatrix())); |
|
947 } |
|
948 |
|
949 return r; |
|
950 } |
|
951 |
|
952 SVGBBox |
|
953 TextRenderedRun::GetFrameUserSpaceRect(nsPresContext* aContext, |
|
954 uint32_t aFlags) const |
|
955 { |
|
956 SVGBBox r = GetRunUserSpaceRect(aContext, aFlags); |
|
957 if (r.IsEmpty()) { |
|
958 return r; |
|
959 } |
|
960 gfxMatrix m = GetTransformFromRunUserSpaceToFrameUserSpace(aContext); |
|
961 return m.TransformBounds(r.ToThebesRect()); |
|
962 } |
|
963 |
|
964 SVGBBox |
|
965 TextRenderedRun::GetUserSpaceRect(nsPresContext* aContext, |
|
966 uint32_t aFlags, |
|
967 const gfxMatrix* aAdditionalTransform) const |
|
968 { |
|
969 SVGBBox r = GetRunUserSpaceRect(aContext, aFlags); |
|
970 if (r.IsEmpty()) { |
|
971 return r; |
|
972 } |
|
973 gfxMatrix m = GetTransformFromRunUserSpaceToUserSpace(aContext); |
|
974 if (aAdditionalTransform) { |
|
975 m.Multiply(*aAdditionalTransform); |
|
976 } |
|
977 return m.TransformBounds(r.ToThebesRect()); |
|
978 } |
|
979 |
|
980 void |
|
981 TextRenderedRun::GetClipEdges(nscoord& aLeftEdge, nscoord& aRightEdge) const |
|
982 { |
|
983 uint32_t contentLength = mFrame->GetContentLength(); |
|
984 if (mTextFrameContentOffset == 0 && |
|
985 mTextFrameContentLength == contentLength) { |
|
986 // If the rendered run covers the entire content, we know we don't need |
|
987 // to clip without having to measure anything. |
|
988 aLeftEdge = 0; |
|
989 aRightEdge = 0; |
|
990 return; |
|
991 } |
|
992 |
|
993 gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated); |
|
994 gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated); |
|
995 |
|
996 // Get the covered content offset/length for this rendered run in skipped |
|
997 // characters, since that is what GetAdvanceWidth expects. |
|
998 uint32_t runOffset, runLength, frameOffset, frameLength; |
|
999 ConvertOriginalToSkipped(it, mTextFrameContentOffset, mTextFrameContentLength, |
|
1000 runOffset, runLength); |
|
1001 |
|
1002 // Get the offset/length of the whole nsTextFrame. |
|
1003 frameOffset = mFrame->GetContentOffset(); |
|
1004 frameLength = mFrame->GetContentLength(); |
|
1005 |
|
1006 // Trim the whole-nsTextFrame offset/length to remove any leading/trailing |
|
1007 // white space, as the nsTextFrame when painting does not include them when |
|
1008 // interpreting clip edges. |
|
1009 nsTextFrame::TrimmedOffsets trimmedOffsets = |
|
1010 mFrame->GetTrimmedOffsets(mFrame->GetContent()->GetText(), true); |
|
1011 TrimOffsets(frameOffset, frameLength, trimmedOffsets); |
|
1012 |
|
1013 // Convert the trimmed whole-nsTextFrame offset/length into skipped |
|
1014 // characters. |
|
1015 ConvertOriginalToSkipped(it, frameOffset, frameLength); |
|
1016 |
|
1017 // Measure the advance width in the text run between the start of |
|
1018 // frame's content and the start of the rendered run's content, |
|
1019 nscoord leftEdge = |
|
1020 textRun->GetAdvanceWidth(frameOffset, runOffset - frameOffset, nullptr); |
|
1021 |
|
1022 // and between the end of the rendered run's content and the end |
|
1023 // of the frame's content. |
|
1024 nscoord rightEdge = |
|
1025 textRun->GetAdvanceWidth(runOffset + runLength, |
|
1026 frameOffset + frameLength - (runOffset + runLength), |
|
1027 nullptr); |
|
1028 |
|
1029 if (textRun->IsRightToLeft()) { |
|
1030 aLeftEdge = rightEdge; |
|
1031 aRightEdge = leftEdge; |
|
1032 } else { |
|
1033 aLeftEdge = leftEdge; |
|
1034 aRightEdge = rightEdge; |
|
1035 } |
|
1036 } |
|
1037 |
|
1038 nscoord |
|
1039 TextRenderedRun::GetAdvanceWidth() const |
|
1040 { |
|
1041 gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated); |
|
1042 gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated); |
|
1043 |
|
1044 uint32_t offset, length; |
|
1045 ConvertOriginalToSkipped(it, mTextFrameContentOffset, mTextFrameContentLength, |
|
1046 offset, length); |
|
1047 |
|
1048 return textRun->GetAdvanceWidth(offset, length, nullptr); |
|
1049 } |
|
1050 |
|
1051 int32_t |
|
1052 TextRenderedRun::GetCharNumAtPosition(nsPresContext* aContext, |
|
1053 const gfxPoint& aPoint) const |
|
1054 { |
|
1055 if (mTextFrameContentLength == 0) { |
|
1056 return -1; |
|
1057 } |
|
1058 |
|
1059 float cssPxPerDevPx = aContext-> |
|
1060 AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel()); |
|
1061 |
|
1062 // Convert the point from user space into run user space, and take |
|
1063 // into account any mFontSizeScaleFactor. |
|
1064 gfxMatrix m = GetTransformFromRunUserSpaceToUserSpace(aContext).Invert(); |
|
1065 gfxPoint p = m.Transform(aPoint) / cssPxPerDevPx * mFontSizeScaleFactor; |
|
1066 |
|
1067 // First check that the point lies vertically between the top and bottom |
|
1068 // edges of the text. |
|
1069 gfxFloat ascent, descent; |
|
1070 GetAscentAndDescentInAppUnits(mFrame, ascent, descent); |
|
1071 |
|
1072 gfxFloat topEdge = mFrame->GetBaseline() - ascent; |
|
1073 gfxFloat bottomEdge = topEdge + ascent + descent; |
|
1074 |
|
1075 if (p.y < aContext->AppUnitsToGfxUnits(topEdge) || |
|
1076 p.y >= aContext->AppUnitsToGfxUnits(bottomEdge)) { |
|
1077 return -1; |
|
1078 } |
|
1079 |
|
1080 gfxSkipCharsIterator it = mFrame->EnsureTextRun(nsTextFrame::eInflated); |
|
1081 gfxTextRun* textRun = mFrame->GetTextRun(nsTextFrame::eInflated); |
|
1082 |
|
1083 // Next check that the point lies horizontally within the left and right |
|
1084 // edges of the text. |
|
1085 uint32_t offset, length; |
|
1086 ConvertOriginalToSkipped(it, mTextFrameContentOffset, mTextFrameContentLength, |
|
1087 offset, length); |
|
1088 gfxFloat runAdvance = |
|
1089 aContext->AppUnitsToGfxUnits(textRun->GetAdvanceWidth(offset, length, |
|
1090 nullptr)); |
|
1091 |
|
1092 if (p.x < 0 || p.x >= runAdvance) { |
|
1093 return -1; |
|
1094 } |
|
1095 |
|
1096 // Finally, measure progressively smaller portions of the rendered run to |
|
1097 // find which glyph it lies within. This will need to change once we |
|
1098 // support letter-spacing and word-spacing. |
|
1099 bool rtl = textRun->IsRightToLeft(); |
|
1100 for (int32_t i = mTextFrameContentLength - 1; i >= 0; i--) { |
|
1101 ConvertOriginalToSkipped(it, mTextFrameContentOffset, i, offset, length); |
|
1102 gfxFloat advance = |
|
1103 aContext->AppUnitsToGfxUnits(textRun->GetAdvanceWidth(offset, length, |
|
1104 nullptr)); |
|
1105 if ((rtl && p.x < runAdvance - advance) || |
|
1106 (!rtl && p.x >= advance)) { |
|
1107 return i; |
|
1108 } |
|
1109 } |
|
1110 return -1; |
|
1111 } |
|
1112 |
|
1113 // ---------------------------------------------------------------------------- |
|
1114 // TextNodeIterator |
|
1115 |
|
1116 enum SubtreePosition |
|
1117 { |
|
1118 eBeforeSubtree, |
|
1119 eWithinSubtree, |
|
1120 eAfterSubtree |
|
1121 }; |
|
1122 |
|
1123 /** |
|
1124 * An iterator class for nsTextNodes that are descendants of a given node, the |
|
1125 * root. Nodes are iterated in document order. An optional subtree can be |
|
1126 * specified, in which case the iterator will track whether the current state of |
|
1127 * the traversal over the tree is within that subtree or is past that subtree. |
|
1128 */ |
|
1129 class TextNodeIterator |
|
1130 { |
|
1131 public: |
|
1132 /** |
|
1133 * Constructs a TextNodeIterator with the specified root node and optional |
|
1134 * subtree. |
|
1135 */ |
|
1136 TextNodeIterator(nsIContent* aRoot, nsIContent* aSubtree = nullptr) |
|
1137 : mRoot(aRoot), |
|
1138 mSubtree(aSubtree == aRoot ? nullptr : aSubtree), |
|
1139 mCurrent(aRoot), |
|
1140 mSubtreePosition(mSubtree ? eBeforeSubtree : eWithinSubtree) |
|
1141 { |
|
1142 NS_ASSERTION(aRoot, "expected non-null root"); |
|
1143 if (!aRoot->IsNodeOfType(nsINode::eTEXT)) { |
|
1144 Next(); |
|
1145 } |
|
1146 } |
|
1147 |
|
1148 /** |
|
1149 * Returns the current nsTextNode, or null if the iterator has finished. |
|
1150 */ |
|
1151 nsTextNode* Current() const |
|
1152 { |
|
1153 return static_cast<nsTextNode*>(mCurrent); |
|
1154 } |
|
1155 |
|
1156 /** |
|
1157 * Advances to the next nsTextNode and returns it, or null if the end of |
|
1158 * iteration has been reached. |
|
1159 */ |
|
1160 nsTextNode* Next(); |
|
1161 |
|
1162 /** |
|
1163 * Returns whether the iterator is currently within the subtree rooted |
|
1164 * at mSubtree. Returns true if we are not tracking a subtree (we consider |
|
1165 * that we're always within the subtree). |
|
1166 */ |
|
1167 bool IsWithinSubtree() const |
|
1168 { |
|
1169 return mSubtreePosition == eWithinSubtree; |
|
1170 } |
|
1171 |
|
1172 /** |
|
1173 * Returns whether the iterator is past the subtree rooted at mSubtree. |
|
1174 * Returns false if we are not tracking a subtree. |
|
1175 */ |
|
1176 bool IsAfterSubtree() const |
|
1177 { |
|
1178 return mSubtreePosition == eAfterSubtree; |
|
1179 } |
|
1180 |
|
1181 private: |
|
1182 /** |
|
1183 * The root under which all nsTextNodes will be iterated over. |
|
1184 */ |
|
1185 nsIContent* mRoot; |
|
1186 |
|
1187 /** |
|
1188 * The node rooting the subtree to track. |
|
1189 */ |
|
1190 nsIContent* mSubtree; |
|
1191 |
|
1192 /** |
|
1193 * The current node during iteration. |
|
1194 */ |
|
1195 nsIContent* mCurrent; |
|
1196 |
|
1197 /** |
|
1198 * The current iterator position relative to mSubtree. |
|
1199 */ |
|
1200 SubtreePosition mSubtreePosition; |
|
1201 }; |
|
1202 |
|
1203 nsTextNode* |
|
1204 TextNodeIterator::Next() |
|
1205 { |
|
1206 // Starting from mCurrent, we do a non-recursive traversal to the next |
|
1207 // nsTextNode beneath mRoot, updating mSubtreePosition appropriately if we |
|
1208 // encounter mSubtree. |
|
1209 if (mCurrent) { |
|
1210 do { |
|
1211 nsIContent* next = IsTextContentElement(mCurrent) ? |
|
1212 mCurrent->GetFirstChild() : |
|
1213 nullptr; |
|
1214 if (next) { |
|
1215 mCurrent = next; |
|
1216 if (mCurrent == mSubtree) { |
|
1217 mSubtreePosition = eWithinSubtree; |
|
1218 } |
|
1219 } else { |
|
1220 for (;;) { |
|
1221 if (mCurrent == mRoot) { |
|
1222 mCurrent = nullptr; |
|
1223 break; |
|
1224 } |
|
1225 if (mCurrent == mSubtree) { |
|
1226 mSubtreePosition = eAfterSubtree; |
|
1227 } |
|
1228 next = mCurrent->GetNextSibling(); |
|
1229 if (next) { |
|
1230 mCurrent = next; |
|
1231 if (mCurrent == mSubtree) { |
|
1232 mSubtreePosition = eWithinSubtree; |
|
1233 } |
|
1234 break; |
|
1235 } |
|
1236 if (mCurrent == mSubtree) { |
|
1237 mSubtreePosition = eAfterSubtree; |
|
1238 } |
|
1239 mCurrent = mCurrent->GetParent(); |
|
1240 } |
|
1241 } |
|
1242 } while (mCurrent && !mCurrent->IsNodeOfType(nsINode::eTEXT)); |
|
1243 } |
|
1244 |
|
1245 return static_cast<nsTextNode*>(mCurrent); |
|
1246 } |
|
1247 |
|
1248 // ---------------------------------------------------------------------------- |
|
1249 // TextNodeCorrespondenceRecorder |
|
1250 |
|
1251 /** |
|
1252 * TextNodeCorrespondence is used as the value of a frame property that |
|
1253 * is stored on all its descendant nsTextFrames. It stores the number of DOM |
|
1254 * characters between it and the previous nsTextFrame that did not have an |
|
1255 * nsTextFrame created for them, due to either not being in a correctly |
|
1256 * parented text content element, or because they were display:none. |
|
1257 * These are called "undisplayed characters". |
|
1258 * |
|
1259 * See also TextNodeCorrespondenceRecorder below, which is what sets the |
|
1260 * frame property. |
|
1261 */ |
|
1262 struct TextNodeCorrespondence |
|
1263 { |
|
1264 TextNodeCorrespondence(uint32_t aUndisplayedCharacters) |
|
1265 : mUndisplayedCharacters(aUndisplayedCharacters) |
|
1266 { |
|
1267 } |
|
1268 |
|
1269 uint32_t mUndisplayedCharacters; |
|
1270 }; |
|
1271 |
|
1272 static void DestroyTextNodeCorrespondence(void* aPropertyValue) |
|
1273 { |
|
1274 delete static_cast<TextNodeCorrespondence*>(aPropertyValue); |
|
1275 } |
|
1276 |
|
1277 NS_DECLARE_FRAME_PROPERTY(TextNodeCorrespondenceProperty, DestroyTextNodeCorrespondence) |
|
1278 |
|
1279 /** |
|
1280 * Returns the number of undisplayed characters before the specified |
|
1281 * nsTextFrame. |
|
1282 */ |
|
1283 static uint32_t |
|
1284 GetUndisplayedCharactersBeforeFrame(nsTextFrame* aFrame) |
|
1285 { |
|
1286 void* value = aFrame->Properties().Get(TextNodeCorrespondenceProperty()); |
|
1287 TextNodeCorrespondence* correspondence = |
|
1288 static_cast<TextNodeCorrespondence*>(value); |
|
1289 if (!correspondence) { |
|
1290 NS_NOTREACHED("expected a TextNodeCorrespondenceProperty on nsTextFrame " |
|
1291 "used for SVG text"); |
|
1292 return 0; |
|
1293 } |
|
1294 return correspondence->mUndisplayedCharacters; |
|
1295 } |
|
1296 |
|
1297 /** |
|
1298 * Traverses the nsTextFrames for an SVGTextFrame and records a |
|
1299 * TextNodeCorrespondenceProperty on each for the number of undisplayed DOM |
|
1300 * characters between each frame. This is done by iterating simultaenously |
|
1301 * over the nsTextNodes and nsTextFrames and noting when nsTextNodes (or |
|
1302 * parts of them) are skipped when finding the next nsTextFrame. |
|
1303 */ |
|
1304 class TextNodeCorrespondenceRecorder |
|
1305 { |
|
1306 public: |
|
1307 /** |
|
1308 * Entry point for the TextNodeCorrespondenceProperty recording. |
|
1309 */ |
|
1310 static void RecordCorrespondence(SVGTextFrame* aRoot); |
|
1311 |
|
1312 private: |
|
1313 TextNodeCorrespondenceRecorder(SVGTextFrame* aRoot) |
|
1314 : mNodeIterator(aRoot->GetContent()), |
|
1315 mPreviousNode(nullptr), |
|
1316 mNodeCharIndex(0) |
|
1317 { |
|
1318 } |
|
1319 |
|
1320 void Record(SVGTextFrame* aRoot); |
|
1321 void TraverseAndRecord(nsIFrame* aFrame); |
|
1322 |
|
1323 /** |
|
1324 * Returns the next non-empty nsTextNode. |
|
1325 */ |
|
1326 nsTextNode* NextNode(); |
|
1327 |
|
1328 /** |
|
1329 * The iterator over the nsTextNodes that we use as we simultaneously |
|
1330 * iterate over the nsTextFrames. |
|
1331 */ |
|
1332 TextNodeIterator mNodeIterator; |
|
1333 |
|
1334 /** |
|
1335 * The previous nsTextNode we iterated over. |
|
1336 */ |
|
1337 nsTextNode* mPreviousNode; |
|
1338 |
|
1339 /** |
|
1340 * The index into the current nsTextNode's character content. |
|
1341 */ |
|
1342 uint32_t mNodeCharIndex; |
|
1343 }; |
|
1344 |
|
1345 /* static */ void |
|
1346 TextNodeCorrespondenceRecorder::RecordCorrespondence(SVGTextFrame* aRoot) |
|
1347 { |
|
1348 TextNodeCorrespondenceRecorder recorder(aRoot); |
|
1349 recorder.Record(aRoot); |
|
1350 } |
|
1351 |
|
1352 void |
|
1353 TextNodeCorrespondenceRecorder::Record(SVGTextFrame* aRoot) |
|
1354 { |
|
1355 if (!mNodeIterator.Current()) { |
|
1356 // If there are no nsTextNodes then there is nothing to do. |
|
1357 return; |
|
1358 } |
|
1359 |
|
1360 // Traverse over all the nsTextFrames and record the number of undisplayed |
|
1361 // characters. |
|
1362 TraverseAndRecord(aRoot); |
|
1363 |
|
1364 // Find how many undisplayed characters there are after the final nsTextFrame. |
|
1365 uint32_t undisplayed = 0; |
|
1366 if (mNodeIterator.Current()) { |
|
1367 if (mPreviousNode && mPreviousNode->TextLength() != mNodeCharIndex) { |
|
1368 // The last nsTextFrame ended part way through an nsTextNode. The |
|
1369 // remaining characters count as undisplayed. |
|
1370 NS_ASSERTION(mNodeCharIndex < mPreviousNode->TextLength(), |
|
1371 "incorrect tracking of undisplayed characters in " |
|
1372 "text nodes"); |
|
1373 undisplayed += mPreviousNode->TextLength() - mNodeCharIndex; |
|
1374 } |
|
1375 // All the remaining nsTextNodes that we iterate must also be undisplayed. |
|
1376 for (nsTextNode* textNode = mNodeIterator.Current(); |
|
1377 textNode; |
|
1378 textNode = NextNode()) { |
|
1379 undisplayed += textNode->TextLength(); |
|
1380 } |
|
1381 } |
|
1382 |
|
1383 // Record the trailing number of undisplayed characters on the |
|
1384 // SVGTextFrame. |
|
1385 aRoot->mTrailingUndisplayedCharacters = undisplayed; |
|
1386 } |
|
1387 |
|
1388 nsTextNode* |
|
1389 TextNodeCorrespondenceRecorder::NextNode() |
|
1390 { |
|
1391 mPreviousNode = mNodeIterator.Current(); |
|
1392 nsTextNode* next; |
|
1393 do { |
|
1394 next = mNodeIterator.Next(); |
|
1395 } while (next && next->TextLength() == 0); |
|
1396 return next; |
|
1397 } |
|
1398 |
|
1399 void |
|
1400 TextNodeCorrespondenceRecorder::TraverseAndRecord(nsIFrame* aFrame) |
|
1401 { |
|
1402 // Recursively iterate over the frame tree, for frames that correspond |
|
1403 // to text content elements. |
|
1404 if (IsTextContentElement(aFrame->GetContent())) { |
|
1405 for (nsIFrame* f = aFrame->GetFirstPrincipalChild(); |
|
1406 f; |
|
1407 f = f->GetNextSibling()) { |
|
1408 TraverseAndRecord(f); |
|
1409 } |
|
1410 return; |
|
1411 } |
|
1412 |
|
1413 nsTextFrame* frame; // The current text frame. |
|
1414 nsTextNode* node; // The text node for the current text frame. |
|
1415 if (!GetNonEmptyTextFrameAndNode(aFrame, frame, node)) { |
|
1416 // If this isn't an nsTextFrame, or is empty, nothing to do. |
|
1417 return; |
|
1418 } |
|
1419 |
|
1420 NS_ASSERTION(frame->GetContentOffset() >= 0, |
|
1421 "don't know how to handle negative content indexes"); |
|
1422 |
|
1423 uint32_t undisplayed = 0; |
|
1424 if (!mPreviousNode) { |
|
1425 // Must be the very first text frame. |
|
1426 NS_ASSERTION(mNodeCharIndex == 0, "incorrect tracking of undisplayed " |
|
1427 "characters in text nodes"); |
|
1428 if (!mNodeIterator.Current()) { |
|
1429 NS_NOTREACHED("incorrect tracking of correspondence between text frames " |
|
1430 "and text nodes"); |
|
1431 } else { |
|
1432 // Each whole nsTextNode we find before we get to the text node for the |
|
1433 // first text frame must be undisplayed. |
|
1434 while (mNodeIterator.Current() != node) { |
|
1435 undisplayed += mNodeIterator.Current()->TextLength(); |
|
1436 NextNode(); |
|
1437 } |
|
1438 // If the first text frame starts at a non-zero content offset, then those |
|
1439 // earlier characters are also undisplayed. |
|
1440 undisplayed += frame->GetContentOffset(); |
|
1441 NextNode(); |
|
1442 } |
|
1443 } else if (mPreviousNode == node) { |
|
1444 // Same text node as last time. |
|
1445 if (static_cast<uint32_t>(frame->GetContentOffset()) != mNodeCharIndex) { |
|
1446 // We have some characters in the middle of the text node |
|
1447 // that are undisplayed. |
|
1448 NS_ASSERTION(mNodeCharIndex < |
|
1449 static_cast<uint32_t>(frame->GetContentOffset()), |
|
1450 "incorrect tracking of undisplayed characters in " |
|
1451 "text nodes"); |
|
1452 undisplayed = frame->GetContentOffset() - mNodeCharIndex; |
|
1453 } |
|
1454 } else { |
|
1455 // Different text node from last time. |
|
1456 if (mPreviousNode->TextLength() != mNodeCharIndex) { |
|
1457 NS_ASSERTION(mNodeCharIndex < mPreviousNode->TextLength(), |
|
1458 "incorrect tracking of undisplayed characters in " |
|
1459 "text nodes"); |
|
1460 // Any trailing characters at the end of the previous nsTextNode are |
|
1461 // undisplayed. |
|
1462 undisplayed = mPreviousNode->TextLength() - mNodeCharIndex; |
|
1463 } |
|
1464 // Each whole nsTextNode we find before we get to the text node for |
|
1465 // the current text frame must be undisplayed. |
|
1466 while (mNodeIterator.Current() != node) { |
|
1467 undisplayed += mNodeIterator.Current()->TextLength(); |
|
1468 NextNode(); |
|
1469 } |
|
1470 // If the current text frame starts at a non-zero content offset, then those |
|
1471 // earlier characters are also undisplayed. |
|
1472 undisplayed += frame->GetContentOffset(); |
|
1473 NextNode(); |
|
1474 } |
|
1475 |
|
1476 // Set the frame property. |
|
1477 frame->Properties().Set(TextNodeCorrespondenceProperty(), |
|
1478 new TextNodeCorrespondence(undisplayed)); |
|
1479 |
|
1480 // Remember how far into the current nsTextNode we are. |
|
1481 mNodeCharIndex = frame->GetContentEnd(); |
|
1482 } |
|
1483 |
|
1484 // ---------------------------------------------------------------------------- |
|
1485 // TextFrameIterator |
|
1486 |
|
1487 /** |
|
1488 * An iterator class for nsTextFrames that are descendants of an |
|
1489 * SVGTextFrame. The iterator can optionally track whether the |
|
1490 * current nsTextFrame is for a descendant of, or past, a given subtree |
|
1491 * content node or frame. (This functionality is used for example by the SVG |
|
1492 * DOM text methods to get only the nsTextFrames for a particular <tspan>.) |
|
1493 * |
|
1494 * TextFrameIterator also tracks and exposes other information about the |
|
1495 * current nsTextFrame: |
|
1496 * |
|
1497 * * how many undisplayed characters came just before it |
|
1498 * * its position (in app units) relative to the SVGTextFrame's anonymous |
|
1499 * block frame |
|
1500 * * what nsInlineFrame corresponding to a <textPath> element it is a |
|
1501 * descendant of |
|
1502 * * what computed dominant-baseline value applies to it |
|
1503 * |
|
1504 * Note that any text frames that are empty -- whose ContentLength() is 0 -- |
|
1505 * will be skipped over. |
|
1506 */ |
|
1507 class TextFrameIterator |
|
1508 { |
|
1509 public: |
|
1510 /** |
|
1511 * Constructs a TextFrameIterator for the specified SVGTextFrame |
|
1512 * with an optional frame subtree to restrict iterated text frames to. |
|
1513 */ |
|
1514 TextFrameIterator(SVGTextFrame* aRoot, nsIFrame* aSubtree = nullptr) |
|
1515 : mRootFrame(aRoot), |
|
1516 mSubtree(aSubtree), |
|
1517 mCurrentFrame(aRoot), |
|
1518 mCurrentPosition(), |
|
1519 mSubtreePosition(mSubtree ? eBeforeSubtree : eWithinSubtree) |
|
1520 { |
|
1521 Init(); |
|
1522 } |
|
1523 |
|
1524 /** |
|
1525 * Constructs a TextFrameIterator for the specified SVGTextFrame |
|
1526 * with an optional frame content subtree to restrict iterated text frames to. |
|
1527 */ |
|
1528 TextFrameIterator(SVGTextFrame* aRoot, nsIContent* aSubtree) |
|
1529 : mRootFrame(aRoot), |
|
1530 mSubtree(aRoot && aSubtree && aSubtree != aRoot->GetContent() ? |
|
1531 aSubtree->GetPrimaryFrame() : |
|
1532 nullptr), |
|
1533 mCurrentFrame(aRoot), |
|
1534 mCurrentPosition(), |
|
1535 mSubtreePosition(mSubtree ? eBeforeSubtree : eWithinSubtree) |
|
1536 { |
|
1537 Init(); |
|
1538 } |
|
1539 |
|
1540 /** |
|
1541 * Returns the root SVGTextFrame this TextFrameIterator is iterating over. |
|
1542 */ |
|
1543 SVGTextFrame* Root() const |
|
1544 { |
|
1545 return mRootFrame; |
|
1546 } |
|
1547 |
|
1548 /** |
|
1549 * Returns the current nsTextFrame. |
|
1550 */ |
|
1551 nsTextFrame* Current() const |
|
1552 { |
|
1553 return do_QueryFrame(mCurrentFrame); |
|
1554 } |
|
1555 |
|
1556 /** |
|
1557 * Returns the number of undisplayed characters in the DOM just before the |
|
1558 * current frame. |
|
1559 */ |
|
1560 uint32_t UndisplayedCharacters() const; |
|
1561 |
|
1562 /** |
|
1563 * Returns the current frame's position, in app units, relative to the |
|
1564 * root SVGTextFrame's anonymous block frame. |
|
1565 */ |
|
1566 nsPoint Position() const |
|
1567 { |
|
1568 return mCurrentPosition; |
|
1569 } |
|
1570 |
|
1571 /** |
|
1572 * Advances to the next nsTextFrame and returns it. |
|
1573 */ |
|
1574 nsTextFrame* Next(); |
|
1575 |
|
1576 /** |
|
1577 * Returns whether the iterator is within the subtree. |
|
1578 */ |
|
1579 bool IsWithinSubtree() const |
|
1580 { |
|
1581 return mSubtreePosition == eWithinSubtree; |
|
1582 } |
|
1583 |
|
1584 /** |
|
1585 * Returns whether the iterator is past the subtree. |
|
1586 */ |
|
1587 bool IsAfterSubtree() const |
|
1588 { |
|
1589 return mSubtreePosition == eAfterSubtree; |
|
1590 } |
|
1591 |
|
1592 /** |
|
1593 * Returns the frame corresponding to the <textPath> element, if we |
|
1594 * are inside one. |
|
1595 */ |
|
1596 nsIFrame* TextPathFrame() const |
|
1597 { |
|
1598 return mTextPathFrames.IsEmpty() ? |
|
1599 nullptr : |
|
1600 mTextPathFrames.ElementAt(mTextPathFrames.Length() - 1); |
|
1601 } |
|
1602 |
|
1603 /** |
|
1604 * Returns the current frame's computed dominant-baseline value. |
|
1605 */ |
|
1606 uint8_t DominantBaseline() const |
|
1607 { |
|
1608 return mBaselines.ElementAt(mBaselines.Length() - 1); |
|
1609 } |
|
1610 |
|
1611 /** |
|
1612 * Finishes the iterator. |
|
1613 */ |
|
1614 void Close() |
|
1615 { |
|
1616 mCurrentFrame = nullptr; |
|
1617 } |
|
1618 |
|
1619 private: |
|
1620 /** |
|
1621 * Initializes the iterator and advances to the first item. |
|
1622 */ |
|
1623 void Init() |
|
1624 { |
|
1625 if (!mRootFrame) { |
|
1626 return; |
|
1627 } |
|
1628 |
|
1629 mBaselines.AppendElement(mRootFrame->StyleSVGReset()->mDominantBaseline); |
|
1630 Next(); |
|
1631 } |
|
1632 |
|
1633 /** |
|
1634 * Pushes the specified frame's computed dominant-baseline value. |
|
1635 * If the value of the property is "auto", then the parent frame's |
|
1636 * computed value is used. |
|
1637 */ |
|
1638 void PushBaseline(nsIFrame* aNextFrame); |
|
1639 |
|
1640 /** |
|
1641 * Pops the current dominant-baseline off the stack. |
|
1642 */ |
|
1643 void PopBaseline(); |
|
1644 |
|
1645 /** |
|
1646 * The root frame we are iterating through. |
|
1647 */ |
|
1648 SVGTextFrame* mRootFrame; |
|
1649 |
|
1650 /** |
|
1651 * The frame for the subtree we are also interested in tracking. |
|
1652 */ |
|
1653 nsIFrame* mSubtree; |
|
1654 |
|
1655 /** |
|
1656 * The current value of the iterator. |
|
1657 */ |
|
1658 nsIFrame* mCurrentFrame; |
|
1659 |
|
1660 /** |
|
1661 * The position, in app units, of the current frame relative to mRootFrame. |
|
1662 */ |
|
1663 nsPoint mCurrentPosition; |
|
1664 |
|
1665 /** |
|
1666 * Stack of frames corresponding to <textPath> elements that are in scope |
|
1667 * for the current frame. |
|
1668 */ |
|
1669 nsAutoTArray<nsIFrame*, 1> mTextPathFrames; |
|
1670 |
|
1671 /** |
|
1672 * Stack of dominant-baseline values to record as we traverse through the |
|
1673 * frame tree. |
|
1674 */ |
|
1675 nsAutoTArray<uint8_t, 8> mBaselines; |
|
1676 |
|
1677 /** |
|
1678 * The iterator's current position relative to mSubtree. |
|
1679 */ |
|
1680 SubtreePosition mSubtreePosition; |
|
1681 }; |
|
1682 |
|
1683 uint32_t |
|
1684 TextFrameIterator::UndisplayedCharacters() const |
|
1685 { |
|
1686 MOZ_ASSERT(!(mRootFrame->GetFirstPrincipalChild() && |
|
1687 NS_SUBTREE_DIRTY(mRootFrame->GetFirstPrincipalChild())), |
|
1688 "should have already reflowed the anonymous block child"); |
|
1689 |
|
1690 if (!mCurrentFrame) { |
|
1691 return mRootFrame->mTrailingUndisplayedCharacters; |
|
1692 } |
|
1693 |
|
1694 nsTextFrame* frame = do_QueryFrame(mCurrentFrame); |
|
1695 return GetUndisplayedCharactersBeforeFrame(frame); |
|
1696 } |
|
1697 |
|
1698 nsTextFrame* |
|
1699 TextFrameIterator::Next() |
|
1700 { |
|
1701 // Starting from mCurrentFrame, we do a non-recursive traversal to the next |
|
1702 // nsTextFrame beneath mRoot, updating mSubtreePosition appropriately if we |
|
1703 // encounter mSubtree. |
|
1704 if (mCurrentFrame) { |
|
1705 do { |
|
1706 nsIFrame* next = IsTextContentElement(mCurrentFrame->GetContent()) ? |
|
1707 mCurrentFrame->GetFirstPrincipalChild() : |
|
1708 nullptr; |
|
1709 if (next) { |
|
1710 // Descend into this frame, and accumulate its position. |
|
1711 mCurrentPosition += next->GetPosition(); |
|
1712 if (next->GetContent()->Tag() == nsGkAtoms::textPath) { |
|
1713 // Record this <textPath> frame. |
|
1714 mTextPathFrames.AppendElement(next); |
|
1715 } |
|
1716 // Record the frame's baseline. |
|
1717 PushBaseline(next); |
|
1718 mCurrentFrame = next; |
|
1719 if (mCurrentFrame == mSubtree) { |
|
1720 // If the current frame is mSubtree, we have now moved into it. |
|
1721 mSubtreePosition = eWithinSubtree; |
|
1722 } |
|
1723 } else { |
|
1724 for (;;) { |
|
1725 // We want to move past the current frame. |
|
1726 if (mCurrentFrame == mRootFrame) { |
|
1727 // If we've reached the root frame, we're finished. |
|
1728 mCurrentFrame = nullptr; |
|
1729 break; |
|
1730 } |
|
1731 // Remove the current frame's position. |
|
1732 mCurrentPosition -= mCurrentFrame->GetPosition(); |
|
1733 if (mCurrentFrame->GetContent()->Tag() == nsGkAtoms::textPath) { |
|
1734 // Pop off the <textPath> frame if this is a <textPath>. |
|
1735 mTextPathFrames.TruncateLength(mTextPathFrames.Length() - 1); |
|
1736 } |
|
1737 // Pop off the current baseline. |
|
1738 PopBaseline(); |
|
1739 if (mCurrentFrame == mSubtree) { |
|
1740 // If this was mSubtree, we have now moved past it. |
|
1741 mSubtreePosition = eAfterSubtree; |
|
1742 } |
|
1743 next = mCurrentFrame->GetNextSibling(); |
|
1744 if (next) { |
|
1745 // Moving to the next sibling. |
|
1746 mCurrentPosition += next->GetPosition(); |
|
1747 if (next->GetContent()->Tag() == nsGkAtoms::textPath) { |
|
1748 // Record this <textPath> frame. |
|
1749 mTextPathFrames.AppendElement(next); |
|
1750 } |
|
1751 // Record the frame's baseline. |
|
1752 PushBaseline(next); |
|
1753 mCurrentFrame = next; |
|
1754 if (mCurrentFrame == mSubtree) { |
|
1755 // If the current frame is mSubtree, we have now moved into it. |
|
1756 mSubtreePosition = eWithinSubtree; |
|
1757 } |
|
1758 break; |
|
1759 } |
|
1760 if (mCurrentFrame == mSubtree) { |
|
1761 // If there is no next sibling frame, and the current frame is |
|
1762 // mSubtree, we have now moved past it. |
|
1763 mSubtreePosition = eAfterSubtree; |
|
1764 } |
|
1765 // Ascend out of this frame. |
|
1766 mCurrentFrame = mCurrentFrame->GetParent(); |
|
1767 } |
|
1768 } |
|
1769 } while (mCurrentFrame && |
|
1770 !IsNonEmptyTextFrame(mCurrentFrame)); |
|
1771 } |
|
1772 |
|
1773 return Current(); |
|
1774 } |
|
1775 |
|
1776 void |
|
1777 TextFrameIterator::PushBaseline(nsIFrame* aNextFrame) |
|
1778 { |
|
1779 uint8_t baseline = aNextFrame->StyleSVGReset()->mDominantBaseline; |
|
1780 if (baseline == NS_STYLE_DOMINANT_BASELINE_AUTO) { |
|
1781 baseline = mBaselines.LastElement(); |
|
1782 } |
|
1783 mBaselines.AppendElement(baseline); |
|
1784 } |
|
1785 |
|
1786 void |
|
1787 TextFrameIterator::PopBaseline() |
|
1788 { |
|
1789 NS_ASSERTION(!mBaselines.IsEmpty(), "popped too many baselines"); |
|
1790 mBaselines.TruncateLength(mBaselines.Length() - 1); |
|
1791 } |
|
1792 |
|
1793 // ----------------------------------------------------------------------------- |
|
1794 // TextRenderedRunIterator |
|
1795 |
|
1796 /** |
|
1797 * Iterator for TextRenderedRun objects for the SVGTextFrame. |
|
1798 */ |
|
1799 class TextRenderedRunIterator |
|
1800 { |
|
1801 public: |
|
1802 /** |
|
1803 * Values for the aFilter argument of the constructor, to indicate which frames |
|
1804 * we should be limited to iterating TextRenderedRun objects for. |
|
1805 */ |
|
1806 enum RenderedRunFilter { |
|
1807 // Iterate TextRenderedRuns for all nsTextFrames. |
|
1808 eAllFrames, |
|
1809 // Iterate only TextRenderedRuns for nsTextFrames that are |
|
1810 // visibility:visible. |
|
1811 eVisibleFrames |
|
1812 }; |
|
1813 |
|
1814 /** |
|
1815 * Constructs a TextRenderedRunIterator with an optional frame subtree to |
|
1816 * restrict iterated rendered runs to. |
|
1817 * |
|
1818 * @param aSVGTextFrame The SVGTextFrame whose rendered runs to iterate |
|
1819 * through. |
|
1820 * @param aFilter Indicates whether to iterate rendered runs for non-visible |
|
1821 * nsTextFrames. |
|
1822 * @param aSubtree An optional frame subtree to restrict iterated rendered |
|
1823 * runs to. |
|
1824 */ |
|
1825 TextRenderedRunIterator(SVGTextFrame* aSVGTextFrame, |
|
1826 RenderedRunFilter aFilter = eAllFrames, |
|
1827 nsIFrame* aSubtree = nullptr) |
|
1828 : mFrameIterator(FrameIfAnonymousChildReflowed(aSVGTextFrame), aSubtree), |
|
1829 mFilter(aFilter), |
|
1830 mTextElementCharIndex(0), |
|
1831 mFrameStartTextElementCharIndex(0), |
|
1832 mFontSizeScaleFactor(aSVGTextFrame->mFontSizeScaleFactor), |
|
1833 mCurrent(First()) |
|
1834 { |
|
1835 } |
|
1836 |
|
1837 /** |
|
1838 * Constructs a TextRenderedRunIterator with a content subtree to restrict |
|
1839 * iterated rendered runs to. |
|
1840 * |
|
1841 * @param aSVGTextFrame The SVGTextFrame whose rendered runs to iterate |
|
1842 * through. |
|
1843 * @param aFilter Indicates whether to iterate rendered runs for non-visible |
|
1844 * nsTextFrames. |
|
1845 * @param aSubtree A content subtree to restrict iterated rendered runs to. |
|
1846 */ |
|
1847 TextRenderedRunIterator(SVGTextFrame* aSVGTextFrame, |
|
1848 RenderedRunFilter aFilter, |
|
1849 nsIContent* aSubtree) |
|
1850 : mFrameIterator(FrameIfAnonymousChildReflowed(aSVGTextFrame), aSubtree), |
|
1851 mFilter(aFilter), |
|
1852 mTextElementCharIndex(0), |
|
1853 mFrameStartTextElementCharIndex(0), |
|
1854 mFontSizeScaleFactor(aSVGTextFrame->mFontSizeScaleFactor), |
|
1855 mCurrent(First()) |
|
1856 { |
|
1857 } |
|
1858 |
|
1859 /** |
|
1860 * Returns the current TextRenderedRun. |
|
1861 */ |
|
1862 TextRenderedRun Current() const |
|
1863 { |
|
1864 return mCurrent; |
|
1865 } |
|
1866 |
|
1867 /** |
|
1868 * Advances to the next TextRenderedRun and returns it. |
|
1869 */ |
|
1870 TextRenderedRun Next(); |
|
1871 |
|
1872 private: |
|
1873 /** |
|
1874 * Returns the root SVGTextFrame this iterator is for. |
|
1875 */ |
|
1876 SVGTextFrame* Root() const |
|
1877 { |
|
1878 return mFrameIterator.Root(); |
|
1879 } |
|
1880 |
|
1881 /** |
|
1882 * Advances to the first TextRenderedRun and returns it. |
|
1883 */ |
|
1884 TextRenderedRun First(); |
|
1885 |
|
1886 /** |
|
1887 * The frame iterator to use. |
|
1888 */ |
|
1889 TextFrameIterator mFrameIterator; |
|
1890 |
|
1891 /** |
|
1892 * The filter indicating which TextRenderedRuns to return. |
|
1893 */ |
|
1894 RenderedRunFilter mFilter; |
|
1895 |
|
1896 /** |
|
1897 * The character index across the entire <text> element we are currently |
|
1898 * up to. |
|
1899 */ |
|
1900 uint32_t mTextElementCharIndex; |
|
1901 |
|
1902 /** |
|
1903 * The character index across the entire <text> for the start of the current |
|
1904 * frame. |
|
1905 */ |
|
1906 uint32_t mFrameStartTextElementCharIndex; |
|
1907 |
|
1908 /** |
|
1909 * The font-size scale factor we used when constructing the nsTextFrames. |
|
1910 */ |
|
1911 double mFontSizeScaleFactor; |
|
1912 |
|
1913 /** |
|
1914 * The current TextRenderedRun. |
|
1915 */ |
|
1916 TextRenderedRun mCurrent; |
|
1917 }; |
|
1918 |
|
1919 TextRenderedRun |
|
1920 TextRenderedRunIterator::Next() |
|
1921 { |
|
1922 if (!mFrameIterator.Current()) { |
|
1923 // If there are no more frames, then there are no more rendered runs to |
|
1924 // return. |
|
1925 mCurrent = TextRenderedRun(); |
|
1926 return mCurrent; |
|
1927 } |
|
1928 |
|
1929 // The values we will use to initialize the TextRenderedRun with. |
|
1930 nsTextFrame* frame; |
|
1931 gfxPoint pt; |
|
1932 double rotate; |
|
1933 nscoord baseline; |
|
1934 uint32_t offset, length; |
|
1935 uint32_t charIndex; |
|
1936 |
|
1937 // We loop, because we want to skip over rendered runs that either aren't |
|
1938 // within our subtree of interest, because they don't match the filter, |
|
1939 // or because they are hidden due to having fallen off the end of a |
|
1940 // <textPath>. |
|
1941 for (;;) { |
|
1942 if (mFrameIterator.IsAfterSubtree()) { |
|
1943 mCurrent = TextRenderedRun(); |
|
1944 return mCurrent; |
|
1945 } |
|
1946 |
|
1947 frame = mFrameIterator.Current(); |
|
1948 |
|
1949 charIndex = mTextElementCharIndex; |
|
1950 |
|
1951 // Find the end of the rendered run, by looking through the |
|
1952 // SVGTextFrame's positions array until we find one that is recorded |
|
1953 // as a run boundary. |
|
1954 uint32_t runStart, runEnd; // XXX Replace runStart with mTextElementCharIndex. |
|
1955 runStart = mTextElementCharIndex; |
|
1956 runEnd = runStart + 1; |
|
1957 while (runEnd < Root()->mPositions.Length() && |
|
1958 !Root()->mPositions[runEnd].mRunBoundary) { |
|
1959 runEnd++; |
|
1960 } |
|
1961 |
|
1962 // Convert the global run start/end indexes into an offset/length into the |
|
1963 // current frame's nsTextNode. |
|
1964 offset = frame->GetContentOffset() + runStart - |
|
1965 mFrameStartTextElementCharIndex; |
|
1966 length = runEnd - runStart; |
|
1967 |
|
1968 // If the end of the frame's content comes before the run boundary we found |
|
1969 // in SVGTextFrame's position array, we need to shorten the rendered run. |
|
1970 uint32_t contentEnd = frame->GetContentEnd(); |
|
1971 if (offset + length > contentEnd) { |
|
1972 length = contentEnd - offset; |
|
1973 } |
|
1974 |
|
1975 NS_ASSERTION(offset >= uint32_t(frame->GetContentOffset()), "invalid offset"); |
|
1976 NS_ASSERTION(offset + length <= contentEnd, "invalid offset or length"); |
|
1977 |
|
1978 // Get the frame's baseline position. |
|
1979 frame->EnsureTextRun(nsTextFrame::eInflated); |
|
1980 baseline = GetBaselinePosition(frame, |
|
1981 frame->GetTextRun(nsTextFrame::eInflated), |
|
1982 mFrameIterator.DominantBaseline()); |
|
1983 |
|
1984 // Trim the offset/length to remove any leading/trailing white space. |
|
1985 uint32_t untrimmedOffset = offset; |
|
1986 uint32_t untrimmedLength = length; |
|
1987 nsTextFrame::TrimmedOffsets trimmedOffsets = |
|
1988 frame->GetTrimmedOffsets(frame->GetContent()->GetText(), true); |
|
1989 TrimOffsets(offset, length, trimmedOffsets); |
|
1990 charIndex += offset - untrimmedOffset; |
|
1991 |
|
1992 // Get the position and rotation of the character that begins this |
|
1993 // rendered run. |
|
1994 pt = Root()->mPositions[charIndex].mPosition; |
|
1995 rotate = Root()->mPositions[charIndex].mAngle; |
|
1996 |
|
1997 // Determine if we should skip this rendered run. |
|
1998 bool skip = !mFrameIterator.IsWithinSubtree() || |
|
1999 Root()->mPositions[mTextElementCharIndex].mHidden; |
|
2000 if (mFilter == eVisibleFrames) { |
|
2001 skip = skip || !frame->StyleVisibility()->IsVisible(); |
|
2002 } |
|
2003 |
|
2004 // Update our global character index to move past the characters |
|
2005 // corresponding to this rendered run. |
|
2006 mTextElementCharIndex += untrimmedLength; |
|
2007 |
|
2008 // If we have moved past the end of the current frame's content, we need to |
|
2009 // advance to the next frame. |
|
2010 if (offset + untrimmedLength >= contentEnd) { |
|
2011 mFrameIterator.Next(); |
|
2012 mTextElementCharIndex += mFrameIterator.UndisplayedCharacters(); |
|
2013 mFrameStartTextElementCharIndex = mTextElementCharIndex; |
|
2014 } |
|
2015 |
|
2016 if (!mFrameIterator.Current()) { |
|
2017 if (skip) { |
|
2018 // That was the last frame, and we skipped this rendered run. So we |
|
2019 // have no rendered run to return. |
|
2020 mCurrent = TextRenderedRun(); |
|
2021 return mCurrent; |
|
2022 } |
|
2023 break; |
|
2024 } |
|
2025 |
|
2026 if (length && !skip) { |
|
2027 // Only return a rendered run if it didn't get collapsed away entirely |
|
2028 // (due to it being all white space) and if we don't want to skip it. |
|
2029 break; |
|
2030 } |
|
2031 } |
|
2032 |
|
2033 mCurrent = TextRenderedRun(frame, pt, Root()->mLengthAdjustScaleFactor, |
|
2034 rotate, mFontSizeScaleFactor, baseline, |
|
2035 offset, length, charIndex); |
|
2036 return mCurrent; |
|
2037 } |
|
2038 |
|
2039 TextRenderedRun |
|
2040 TextRenderedRunIterator::First() |
|
2041 { |
|
2042 if (!mFrameIterator.Current()) { |
|
2043 return TextRenderedRun(); |
|
2044 } |
|
2045 |
|
2046 if (Root()->mPositions.IsEmpty()) { |
|
2047 mFrameIterator.Close(); |
|
2048 return TextRenderedRun(); |
|
2049 } |
|
2050 |
|
2051 // Get the character index for the start of this rendered run, by skipping |
|
2052 // any undisplayed characters. |
|
2053 mTextElementCharIndex = mFrameIterator.UndisplayedCharacters(); |
|
2054 mFrameStartTextElementCharIndex = mTextElementCharIndex; |
|
2055 |
|
2056 return Next(); |
|
2057 } |
|
2058 |
|
2059 // ----------------------------------------------------------------------------- |
|
2060 // CharIterator |
|
2061 |
|
2062 /** |
|
2063 * Iterator for characters within an SVGTextFrame. |
|
2064 */ |
|
2065 class CharIterator |
|
2066 { |
|
2067 public: |
|
2068 /** |
|
2069 * Values for the aFilter argument of the constructor, to indicate which |
|
2070 * characters we should be iterating over. |
|
2071 */ |
|
2072 enum CharacterFilter { |
|
2073 // Iterate over all original characters from the DOM that are within valid |
|
2074 // text content elements. |
|
2075 eOriginal, |
|
2076 // Iterate only over characters that are addressable by the positioning |
|
2077 // attributes x="", y="", etc. This includes all characters after |
|
2078 // collapsing white space as required by the value of 'white-space'. |
|
2079 eAddressable, |
|
2080 // Iterate only over characters that are the first of clusters or ligature |
|
2081 // groups. |
|
2082 eClusterAndLigatureGroupStart, |
|
2083 // Iterate only over characters that are part of a cluster or ligature |
|
2084 // group but not the first character. |
|
2085 eClusterOrLigatureGroupMiddle |
|
2086 }; |
|
2087 |
|
2088 /** |
|
2089 * Constructs a CharIterator. |
|
2090 * |
|
2091 * @param aSVGTextFrame The SVGTextFrame whose characters to iterate |
|
2092 * through. |
|
2093 * @param aFilter Indicates which characters to iterate over. |
|
2094 * @param aSubtree A content subtree to track whether the current character |
|
2095 * is within. |
|
2096 */ |
|
2097 CharIterator(SVGTextFrame* aSVGTextFrame, |
|
2098 CharacterFilter aFilter, |
|
2099 nsIContent* aSubtree = nullptr); |
|
2100 |
|
2101 /** |
|
2102 * Returns whether the iterator is finished. |
|
2103 */ |
|
2104 bool AtEnd() const |
|
2105 { |
|
2106 return !mFrameIterator.Current(); |
|
2107 } |
|
2108 |
|
2109 /** |
|
2110 * Advances to the next matching character. Returns true if there was a |
|
2111 * character to advance to, and false otherwise. |
|
2112 */ |
|
2113 bool Next(); |
|
2114 |
|
2115 /** |
|
2116 * Advances ahead aCount matching characters. Returns true if there were |
|
2117 * enough characters to advance past, and false otherwise. |
|
2118 */ |
|
2119 bool Next(uint32_t aCount); |
|
2120 |
|
2121 /** |
|
2122 * Advances ahead up to aCount matching characters. |
|
2123 */ |
|
2124 void NextWithinSubtree(uint32_t aCount); |
|
2125 |
|
2126 /** |
|
2127 * Advances to the character with the specified index. The index is in the |
|
2128 * space of original characters (i.e., all DOM characters under the <text> |
|
2129 * that are within valid text content elements). |
|
2130 */ |
|
2131 bool AdvanceToCharacter(uint32_t aTextElementCharIndex); |
|
2132 |
|
2133 /** |
|
2134 * Advances to the first matching character after the current nsTextFrame. |
|
2135 */ |
|
2136 bool AdvancePastCurrentFrame(); |
|
2137 |
|
2138 /** |
|
2139 * Advances to the first matching character after the frames within |
|
2140 * the current <textPath>. |
|
2141 */ |
|
2142 bool AdvancePastCurrentTextPathFrame(); |
|
2143 |
|
2144 /** |
|
2145 * Advances to the first matching character of the subtree. Returns true |
|
2146 * if we successfully advance to the subtree, or if we are already within |
|
2147 * the subtree. Returns false if we are past the subtree. |
|
2148 */ |
|
2149 bool AdvanceToSubtree(); |
|
2150 |
|
2151 /** |
|
2152 * Returns the nsTextFrame for the current character. |
|
2153 */ |
|
2154 nsTextFrame* TextFrame() const |
|
2155 { |
|
2156 return mFrameIterator.Current(); |
|
2157 } |
|
2158 |
|
2159 /** |
|
2160 * Returns whether the iterator is within the subtree. |
|
2161 */ |
|
2162 bool IsWithinSubtree() const |
|
2163 { |
|
2164 return mFrameIterator.IsWithinSubtree(); |
|
2165 } |
|
2166 |
|
2167 /** |
|
2168 * Returns whether the iterator is past the subtree. |
|
2169 */ |
|
2170 bool IsAfterSubtree() const |
|
2171 { |
|
2172 return mFrameIterator.IsAfterSubtree(); |
|
2173 } |
|
2174 |
|
2175 /** |
|
2176 * Returns whether the current character is a skipped character. |
|
2177 */ |
|
2178 bool IsOriginalCharSkipped() const |
|
2179 { |
|
2180 return mSkipCharsIterator.IsOriginalCharSkipped(); |
|
2181 } |
|
2182 |
|
2183 /** |
|
2184 * Returns whether the current character is the start of a cluster and |
|
2185 * ligature group. |
|
2186 */ |
|
2187 bool IsClusterAndLigatureGroupStart() const; |
|
2188 |
|
2189 /** |
|
2190 * Returns whether the current character is trimmed away when painting, |
|
2191 * due to it being leading/trailing white space. |
|
2192 */ |
|
2193 bool IsOriginalCharTrimmed() const; |
|
2194 |
|
2195 /** |
|
2196 * Returns whether the current character is unaddressable from the SVG glyph |
|
2197 * positioning attributes. |
|
2198 */ |
|
2199 bool IsOriginalCharUnaddressable() const |
|
2200 { |
|
2201 return IsOriginalCharSkipped() || IsOriginalCharTrimmed(); |
|
2202 } |
|
2203 |
|
2204 /** |
|
2205 * Returns the text run for the current character. |
|
2206 */ |
|
2207 gfxTextRun* TextRun() const |
|
2208 { |
|
2209 return mTextRun; |
|
2210 } |
|
2211 |
|
2212 /** |
|
2213 * Returns the current character index. |
|
2214 */ |
|
2215 uint32_t TextElementCharIndex() const |
|
2216 { |
|
2217 return mTextElementCharIndex; |
|
2218 } |
|
2219 |
|
2220 /** |
|
2221 * Returns the character index for the start of the cluster/ligature group it |
|
2222 * is part of. |
|
2223 */ |
|
2224 uint32_t GlyphStartTextElementCharIndex() const |
|
2225 { |
|
2226 return mGlyphStartTextElementCharIndex; |
|
2227 } |
|
2228 |
|
2229 /** |
|
2230 * Returns the number of undisplayed characters between the beginning of |
|
2231 * the glyph and the current character. |
|
2232 */ |
|
2233 uint32_t GlyphUndisplayedCharacters() const |
|
2234 { |
|
2235 return mGlyphUndisplayedCharacters; |
|
2236 } |
|
2237 |
|
2238 /** |
|
2239 * Gets the original character offsets within the nsTextNode for the |
|
2240 * cluster/ligature group the current character is a part of. |
|
2241 * |
|
2242 * @param aOriginalOffset The offset of the start of the cluster/ligature |
|
2243 * group (output). |
|
2244 * @param aOriginalLength The length of cluster/ligature group (output). |
|
2245 */ |
|
2246 void GetOriginalGlyphOffsets(uint32_t& aOriginalOffset, |
|
2247 uint32_t& aOriginalLength) const; |
|
2248 |
|
2249 /** |
|
2250 * Gets the advance, in user units, of the glyph the current character is |
|
2251 * part of. |
|
2252 * |
|
2253 * @param aContext The context to use for unit conversions. |
|
2254 */ |
|
2255 gfxFloat GetGlyphAdvance(nsPresContext* aContext) const; |
|
2256 |
|
2257 /** |
|
2258 * Gets the advance, in user units, of the current character. If the |
|
2259 * character is a part of ligature, then the advance returned will be |
|
2260 * a fraction of the ligature glyph's advance. |
|
2261 * |
|
2262 * @param aContext The context to use for unit conversions. |
|
2263 */ |
|
2264 gfxFloat GetAdvance(nsPresContext* aContext) const; |
|
2265 |
|
2266 /** |
|
2267 * Gets the specified partial advance of the glyph the current character is |
|
2268 * part of. The partial advance is measured from the first character |
|
2269 * corresponding to the glyph until the specified part length. |
|
2270 * |
|
2271 * The part length value does not include any undisplayed characters in the |
|
2272 * middle of the cluster/ligature group. For example, if you have: |
|
2273 * |
|
2274 * <text>f<tspan display="none">x</tspan>i</text> |
|
2275 * |
|
2276 * and the "f" and "i" are ligaturized, then calling GetGlyphPartialAdvance |
|
2277 * with aPartLength values will have the following results: |
|
2278 * |
|
2279 * 0 => 0 |
|
2280 * 1 => adv("fi") / 2 |
|
2281 * 2 => adv("fi") |
|
2282 * |
|
2283 * @param aPartLength The number of characters in the cluster/ligature group |
|
2284 * to measure. |
|
2285 * @param aContext The context to use for unit conversions. |
|
2286 */ |
|
2287 gfxFloat GetGlyphPartialAdvance(uint32_t aPartLength, |
|
2288 nsPresContext* aContext) const; |
|
2289 |
|
2290 /** |
|
2291 * Returns the frame corresponding to the <textPath> that the current |
|
2292 * character is within. |
|
2293 */ |
|
2294 nsIFrame* TextPathFrame() const |
|
2295 { |
|
2296 return mFrameIterator.TextPathFrame(); |
|
2297 } |
|
2298 |
|
2299 private: |
|
2300 /** |
|
2301 * Advances to the next character without checking it against the filter. |
|
2302 * Returns true if there was a next character to advance to, or false |
|
2303 * otherwise. |
|
2304 */ |
|
2305 bool NextCharacter(); |
|
2306 |
|
2307 /** |
|
2308 * Returns whether the current character matches the filter. |
|
2309 */ |
|
2310 bool MatchesFilter() const; |
|
2311 |
|
2312 /** |
|
2313 * If this is the start of a glyph, record it. |
|
2314 */ |
|
2315 void UpdateGlyphStartTextElementCharIndex() { |
|
2316 if (!IsOriginalCharSkipped() && IsClusterAndLigatureGroupStart()) { |
|
2317 mGlyphStartTextElementCharIndex = mTextElementCharIndex; |
|
2318 mGlyphUndisplayedCharacters = 0; |
|
2319 } |
|
2320 } |
|
2321 |
|
2322 /** |
|
2323 * The filter to use. |
|
2324 */ |
|
2325 CharacterFilter mFilter; |
|
2326 |
|
2327 /** |
|
2328 * The iterator for text frames. |
|
2329 */ |
|
2330 TextFrameIterator mFrameIterator; |
|
2331 |
|
2332 /** |
|
2333 * A gfxSkipCharsIterator for the text frame the current character is |
|
2334 * a part of. |
|
2335 */ |
|
2336 gfxSkipCharsIterator mSkipCharsIterator; |
|
2337 |
|
2338 // Cache for information computed by IsOriginalCharTrimmed. |
|
2339 mutable nsTextFrame* mFrameForTrimCheck; |
|
2340 mutable uint32_t mTrimmedOffset; |
|
2341 mutable uint32_t mTrimmedLength; |
|
2342 |
|
2343 /** |
|
2344 * The text run the current character is a part of. |
|
2345 */ |
|
2346 gfxTextRun* mTextRun; |
|
2347 |
|
2348 /** |
|
2349 * The current character's index. |
|
2350 */ |
|
2351 uint32_t mTextElementCharIndex; |
|
2352 |
|
2353 /** |
|
2354 * The index of the character that starts the cluster/ligature group the |
|
2355 * current character is a part of. |
|
2356 */ |
|
2357 uint32_t mGlyphStartTextElementCharIndex; |
|
2358 |
|
2359 /** |
|
2360 * If we are iterating in mode eClusterOrLigatureGroupMiddle, then |
|
2361 * this tracks how many undisplayed characters were encountered |
|
2362 * between the start of this glyph (at mGlyphStartTextElementCharIndex) |
|
2363 * and the current character (at mTextElementCharIndex). |
|
2364 */ |
|
2365 uint32_t mGlyphUndisplayedCharacters; |
|
2366 |
|
2367 /** |
|
2368 * The scale factor to apply to glyph advances returned by |
|
2369 * GetGlyphAdvance etc. to take into account textLength="". |
|
2370 */ |
|
2371 float mLengthAdjustScaleFactor; |
|
2372 }; |
|
2373 |
|
2374 CharIterator::CharIterator(SVGTextFrame* aSVGTextFrame, |
|
2375 CharIterator::CharacterFilter aFilter, |
|
2376 nsIContent* aSubtree) |
|
2377 : mFilter(aFilter), |
|
2378 mFrameIterator(FrameIfAnonymousChildReflowed(aSVGTextFrame), aSubtree), |
|
2379 mFrameForTrimCheck(nullptr), |
|
2380 mTrimmedOffset(0), |
|
2381 mTrimmedLength(0), |
|
2382 mTextElementCharIndex(0), |
|
2383 mGlyphStartTextElementCharIndex(0), |
|
2384 mLengthAdjustScaleFactor(aSVGTextFrame->mLengthAdjustScaleFactor) |
|
2385 { |
|
2386 if (!AtEnd()) { |
|
2387 mSkipCharsIterator = TextFrame()->EnsureTextRun(nsTextFrame::eInflated); |
|
2388 mTextRun = TextFrame()->GetTextRun(nsTextFrame::eInflated); |
|
2389 mTextElementCharIndex = mFrameIterator.UndisplayedCharacters(); |
|
2390 UpdateGlyphStartTextElementCharIndex(); |
|
2391 if (!MatchesFilter()) { |
|
2392 Next(); |
|
2393 } |
|
2394 } |
|
2395 } |
|
2396 |
|
2397 bool |
|
2398 CharIterator::Next() |
|
2399 { |
|
2400 while (NextCharacter()) { |
|
2401 if (MatchesFilter()) { |
|
2402 return true; |
|
2403 } |
|
2404 } |
|
2405 return false; |
|
2406 } |
|
2407 |
|
2408 bool |
|
2409 CharIterator::Next(uint32_t aCount) |
|
2410 { |
|
2411 if (aCount == 0 && AtEnd()) { |
|
2412 return false; |
|
2413 } |
|
2414 while (aCount) { |
|
2415 if (!Next()) { |
|
2416 return false; |
|
2417 } |
|
2418 aCount--; |
|
2419 } |
|
2420 return true; |
|
2421 } |
|
2422 |
|
2423 void |
|
2424 CharIterator::NextWithinSubtree(uint32_t aCount) |
|
2425 { |
|
2426 while (IsWithinSubtree() && aCount) { |
|
2427 --aCount; |
|
2428 if (!Next()) { |
|
2429 return; |
|
2430 } |
|
2431 } |
|
2432 } |
|
2433 |
|
2434 bool |
|
2435 CharIterator::AdvanceToCharacter(uint32_t aTextElementCharIndex) |
|
2436 { |
|
2437 while (mTextElementCharIndex < aTextElementCharIndex) { |
|
2438 if (!Next()) { |
|
2439 return false; |
|
2440 } |
|
2441 } |
|
2442 return true; |
|
2443 } |
|
2444 |
|
2445 bool |
|
2446 CharIterator::AdvancePastCurrentFrame() |
|
2447 { |
|
2448 // XXX Can do this better than one character at a time if it matters. |
|
2449 nsTextFrame* currentFrame = TextFrame(); |
|
2450 do { |
|
2451 if (!Next()) { |
|
2452 return false; |
|
2453 } |
|
2454 } while (TextFrame() == currentFrame); |
|
2455 return true; |
|
2456 } |
|
2457 |
|
2458 bool |
|
2459 CharIterator::AdvancePastCurrentTextPathFrame() |
|
2460 { |
|
2461 nsIFrame* currentTextPathFrame = TextPathFrame(); |
|
2462 NS_ASSERTION(currentTextPathFrame, |
|
2463 "expected AdvancePastCurrentTextPathFrame to be called only " |
|
2464 "within a text path frame"); |
|
2465 do { |
|
2466 if (!AdvancePastCurrentFrame()) { |
|
2467 return false; |
|
2468 } |
|
2469 } while (TextPathFrame() == currentTextPathFrame); |
|
2470 return true; |
|
2471 } |
|
2472 |
|
2473 bool |
|
2474 CharIterator::AdvanceToSubtree() |
|
2475 { |
|
2476 while (!IsWithinSubtree()) { |
|
2477 if (IsAfterSubtree()) { |
|
2478 return false; |
|
2479 } |
|
2480 if (!AdvancePastCurrentFrame()) { |
|
2481 return false; |
|
2482 } |
|
2483 } |
|
2484 return true; |
|
2485 } |
|
2486 |
|
2487 bool |
|
2488 CharIterator::IsClusterAndLigatureGroupStart() const |
|
2489 { |
|
2490 return mTextRun->IsLigatureGroupStart(mSkipCharsIterator.GetSkippedOffset()) && |
|
2491 mTextRun->IsClusterStart(mSkipCharsIterator.GetSkippedOffset()); |
|
2492 } |
|
2493 |
|
2494 bool |
|
2495 CharIterator::IsOriginalCharTrimmed() const |
|
2496 { |
|
2497 if (mFrameForTrimCheck != TextFrame()) { |
|
2498 // Since we do a lot of trim checking, we cache the trimmed offsets and |
|
2499 // lengths while we are in the same frame. |
|
2500 mFrameForTrimCheck = TextFrame(); |
|
2501 uint32_t offset = mFrameForTrimCheck->GetContentOffset(); |
|
2502 uint32_t length = mFrameForTrimCheck->GetContentLength(); |
|
2503 nsIContent* content = mFrameForTrimCheck->GetContent(); |
|
2504 nsTextFrame::TrimmedOffsets trim = |
|
2505 mFrameForTrimCheck->GetTrimmedOffsets(content->GetText(), true); |
|
2506 TrimOffsets(offset, length, trim); |
|
2507 mTrimmedOffset = offset; |
|
2508 mTrimmedLength = length; |
|
2509 } |
|
2510 |
|
2511 // A character is trimmed if it is outside the mTrimmedOffset/mTrimmedLength |
|
2512 // range and it is not a significant newline character. |
|
2513 uint32_t index = mSkipCharsIterator.GetOriginalOffset(); |
|
2514 return !((index >= mTrimmedOffset && |
|
2515 index < mTrimmedOffset + mTrimmedLength) || |
|
2516 (index >= mTrimmedOffset + mTrimmedLength && |
|
2517 mFrameForTrimCheck->StyleText()->NewlineIsSignificant() && |
|
2518 mFrameForTrimCheck->GetContent()->GetText()->CharAt(index) == '\n')); |
|
2519 } |
|
2520 |
|
2521 void |
|
2522 CharIterator::GetOriginalGlyphOffsets(uint32_t& aOriginalOffset, |
|
2523 uint32_t& aOriginalLength) const |
|
2524 { |
|
2525 gfxSkipCharsIterator it = TextFrame()->EnsureTextRun(nsTextFrame::eInflated); |
|
2526 it.SetOriginalOffset(mSkipCharsIterator.GetOriginalOffset() - |
|
2527 (mTextElementCharIndex - |
|
2528 mGlyphStartTextElementCharIndex - |
|
2529 mGlyphUndisplayedCharacters)); |
|
2530 |
|
2531 while (it.GetSkippedOffset() > 0 && |
|
2532 (!mTextRun->IsClusterStart(it.GetSkippedOffset()) || |
|
2533 !mTextRun->IsLigatureGroupStart(it.GetSkippedOffset()))) { |
|
2534 it.AdvanceSkipped(-1); |
|
2535 } |
|
2536 |
|
2537 aOriginalOffset = it.GetOriginalOffset(); |
|
2538 |
|
2539 // Find the end of the cluster/ligature group. |
|
2540 it.SetOriginalOffset(mSkipCharsIterator.GetOriginalOffset()); |
|
2541 do { |
|
2542 it.AdvanceSkipped(1); |
|
2543 } while (it.GetSkippedOffset() < mTextRun->GetLength() && |
|
2544 (!mTextRun->IsClusterStart(it.GetSkippedOffset()) || |
|
2545 !mTextRun->IsLigatureGroupStart(it.GetSkippedOffset()))); |
|
2546 |
|
2547 aOriginalLength = it.GetOriginalOffset() - aOriginalOffset; |
|
2548 } |
|
2549 |
|
2550 gfxFloat |
|
2551 CharIterator::GetGlyphAdvance(nsPresContext* aContext) const |
|
2552 { |
|
2553 uint32_t offset, length; |
|
2554 GetOriginalGlyphOffsets(offset, length); |
|
2555 |
|
2556 gfxSkipCharsIterator it = TextFrame()->EnsureTextRun(nsTextFrame::eInflated); |
|
2557 ConvertOriginalToSkipped(it, offset, length); |
|
2558 |
|
2559 float cssPxPerDevPx = aContext-> |
|
2560 AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel()); |
|
2561 |
|
2562 gfxFloat advance = mTextRun->GetAdvanceWidth(offset, length, nullptr); |
|
2563 return aContext->AppUnitsToGfxUnits(advance) * |
|
2564 mLengthAdjustScaleFactor * cssPxPerDevPx; |
|
2565 } |
|
2566 |
|
2567 gfxFloat |
|
2568 CharIterator::GetAdvance(nsPresContext* aContext) const |
|
2569 { |
|
2570 float cssPxPerDevPx = aContext-> |
|
2571 AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel()); |
|
2572 |
|
2573 gfxFloat advance = |
|
2574 mTextRun->GetAdvanceWidth(mSkipCharsIterator.GetSkippedOffset(), 1, nullptr); |
|
2575 return aContext->AppUnitsToGfxUnits(advance) * |
|
2576 mLengthAdjustScaleFactor * cssPxPerDevPx; |
|
2577 } |
|
2578 |
|
2579 gfxFloat |
|
2580 CharIterator::GetGlyphPartialAdvance(uint32_t aPartLength, |
|
2581 nsPresContext* aContext) const |
|
2582 { |
|
2583 uint32_t offset, length; |
|
2584 GetOriginalGlyphOffsets(offset, length); |
|
2585 |
|
2586 NS_ASSERTION(aPartLength <= length, "invalid aPartLength value"); |
|
2587 length = aPartLength; |
|
2588 |
|
2589 gfxSkipCharsIterator it = TextFrame()->EnsureTextRun(nsTextFrame::eInflated); |
|
2590 ConvertOriginalToSkipped(it, offset, length); |
|
2591 |
|
2592 float cssPxPerDevPx = aContext-> |
|
2593 AppUnitsToFloatCSSPixels(aContext->AppUnitsPerDevPixel()); |
|
2594 |
|
2595 gfxFloat advance = mTextRun->GetAdvanceWidth(offset, length, nullptr); |
|
2596 return aContext->AppUnitsToGfxUnits(advance) * |
|
2597 mLengthAdjustScaleFactor * cssPxPerDevPx; |
|
2598 } |
|
2599 |
|
2600 bool |
|
2601 CharIterator::NextCharacter() |
|
2602 { |
|
2603 if (AtEnd()) { |
|
2604 return false; |
|
2605 } |
|
2606 |
|
2607 mTextElementCharIndex++; |
|
2608 |
|
2609 // Advance within the current text run. |
|
2610 mSkipCharsIterator.AdvanceOriginal(1); |
|
2611 if (mSkipCharsIterator.GetOriginalOffset() < TextFrame()->GetContentEnd()) { |
|
2612 // We're still within the part of the text run for the current text frame. |
|
2613 UpdateGlyphStartTextElementCharIndex(); |
|
2614 return true; |
|
2615 } |
|
2616 |
|
2617 // Advance to the next frame. |
|
2618 mFrameIterator.Next(); |
|
2619 |
|
2620 // Skip any undisplayed characters. |
|
2621 uint32_t undisplayed = mFrameIterator.UndisplayedCharacters(); |
|
2622 mGlyphUndisplayedCharacters += undisplayed; |
|
2623 mTextElementCharIndex += undisplayed; |
|
2624 if (!TextFrame()) { |
|
2625 // We're at the end. |
|
2626 mSkipCharsIterator = gfxSkipCharsIterator(); |
|
2627 return false; |
|
2628 } |
|
2629 |
|
2630 mSkipCharsIterator = TextFrame()->EnsureTextRun(nsTextFrame::eInflated); |
|
2631 mTextRun = TextFrame()->GetTextRun(nsTextFrame::eInflated); |
|
2632 UpdateGlyphStartTextElementCharIndex(); |
|
2633 return true; |
|
2634 } |
|
2635 |
|
2636 bool |
|
2637 CharIterator::MatchesFilter() const |
|
2638 { |
|
2639 if (mFilter == eOriginal) { |
|
2640 return true; |
|
2641 } |
|
2642 |
|
2643 if (IsOriginalCharSkipped()) { |
|
2644 return false; |
|
2645 } |
|
2646 |
|
2647 if (mFilter == eAddressable) { |
|
2648 return !IsOriginalCharUnaddressable(); |
|
2649 } |
|
2650 |
|
2651 return (mFilter == eClusterAndLigatureGroupStart) == |
|
2652 IsClusterAndLigatureGroupStart(); |
|
2653 } |
|
2654 |
|
2655 // ----------------------------------------------------------------------------- |
|
2656 // nsCharClipDisplayItem |
|
2657 |
|
2658 /** |
|
2659 * An nsCharClipDisplayItem that obtains its left and right clip edges from a |
|
2660 * TextRenderedRun object. |
|
2661 */ |
|
2662 class SVGCharClipDisplayItem : public nsCharClipDisplayItem { |
|
2663 public: |
|
2664 SVGCharClipDisplayItem(const TextRenderedRun& aRun) |
|
2665 : nsCharClipDisplayItem(aRun.mFrame) |
|
2666 { |
|
2667 aRun.GetClipEdges(mLeftEdge, mRightEdge); |
|
2668 } |
|
2669 |
|
2670 NS_DISPLAY_DECL_NAME("SVGText", TYPE_TEXT) |
|
2671 }; |
|
2672 |
|
2673 // ----------------------------------------------------------------------------- |
|
2674 // SVGTextDrawPathCallbacks |
|
2675 |
|
2676 /** |
|
2677 * Text frame draw callback class that paints the text and text decoration parts |
|
2678 * of an nsTextFrame using SVG painting properties, and selection backgrounds |
|
2679 * and decorations as they would normally. |
|
2680 * |
|
2681 * An instance of this class is passed to nsTextFrame::PaintText if painting |
|
2682 * cannot be done directly (e.g. if we are using an SVG pattern fill, stroking |
|
2683 * the text, etc.). |
|
2684 */ |
|
2685 class SVGTextDrawPathCallbacks : public nsTextFrame::DrawPathCallbacks |
|
2686 { |
|
2687 public: |
|
2688 /** |
|
2689 * Constructs an SVGTextDrawPathCallbacks. |
|
2690 * |
|
2691 * @param aContext The context to use for painting. |
|
2692 * @param aFrame The nsTextFrame to paint. |
|
2693 * @param aCanvasTM The transformation matrix to set when painting; this |
|
2694 * should be the FOR_OUTERSVG_TM canvas TM of the text, so that |
|
2695 * paint servers are painted correctly. |
|
2696 * @param aShouldPaintSVGGlyphs Whether SVG glyphs should be painted. |
|
2697 */ |
|
2698 SVGTextDrawPathCallbacks(nsRenderingContext* aContext, |
|
2699 nsTextFrame* aFrame, |
|
2700 const gfxMatrix& aCanvasTM, |
|
2701 bool aShouldPaintSVGGlyphs) |
|
2702 : DrawPathCallbacks(aShouldPaintSVGGlyphs), |
|
2703 gfx(aContext->ThebesContext()), |
|
2704 mRenderMode(SVGAutoRenderState::GetRenderMode(aContext)), |
|
2705 mFrame(aFrame), |
|
2706 mCanvasTM(aCanvasTM) |
|
2707 { |
|
2708 } |
|
2709 |
|
2710 void NotifyBeforeText(nscolor aColor) MOZ_OVERRIDE; |
|
2711 void NotifyGlyphPathEmitted() MOZ_OVERRIDE; |
|
2712 void NotifyBeforeSVGGlyphPainted() MOZ_OVERRIDE; |
|
2713 void NotifyAfterSVGGlyphPainted() MOZ_OVERRIDE; |
|
2714 void NotifyAfterText() MOZ_OVERRIDE; |
|
2715 void NotifyBeforeSelectionBackground(nscolor aColor) MOZ_OVERRIDE; |
|
2716 void NotifySelectionBackgroundPathEmitted() MOZ_OVERRIDE; |
|
2717 void NotifyBeforeDecorationLine(nscolor aColor) MOZ_OVERRIDE; |
|
2718 void NotifyDecorationLinePathEmitted() MOZ_OVERRIDE; |
|
2719 void NotifyBeforeSelectionDecorationLine(nscolor aColor) MOZ_OVERRIDE; |
|
2720 void NotifySelectionDecorationLinePathEmitted() MOZ_OVERRIDE; |
|
2721 |
|
2722 private: |
|
2723 void FillWithOpacity(); |
|
2724 |
|
2725 void SetupContext(); |
|
2726 |
|
2727 /** |
|
2728 * Paints a piece of text geometry. This is called when glyphs |
|
2729 * or text decorations have been emitted to the gfxContext. |
|
2730 */ |
|
2731 void HandleTextGeometry(); |
|
2732 |
|
2733 /** |
|
2734 * Sets the gfxContext paint to the appropriate color or pattern |
|
2735 * for filling text geometry. |
|
2736 */ |
|
2737 bool SetFillColor(); |
|
2738 |
|
2739 /** |
|
2740 * Fills and strokes a piece of text geometry, using group opacity |
|
2741 * if the selection style requires it. |
|
2742 */ |
|
2743 void FillAndStrokeGeometry(); |
|
2744 |
|
2745 /** |
|
2746 * Fills a piece of text geometry. |
|
2747 */ |
|
2748 void FillGeometry(); |
|
2749 |
|
2750 /** |
|
2751 * Strokes a piece of text geometry. |
|
2752 */ |
|
2753 void StrokeGeometry(); |
|
2754 |
|
2755 gfxContext* gfx; |
|
2756 uint16_t mRenderMode; |
|
2757 nsTextFrame* mFrame; |
|
2758 const gfxMatrix& mCanvasTM; |
|
2759 |
|
2760 /** |
|
2761 * The color that we were last told from one of the path callback functions. |
|
2762 * This color can be the special NS_SAME_AS_FOREGROUND_COLOR, |
|
2763 * NS_40PERCENT_FOREGROUND_COLOR and NS_TRANSPARENT colors when we are |
|
2764 * painting selections or IME decorations. |
|
2765 */ |
|
2766 nscolor mColor; |
|
2767 }; |
|
2768 |
|
2769 void |
|
2770 SVGTextDrawPathCallbacks::NotifyBeforeText(nscolor aColor) |
|
2771 { |
|
2772 mColor = aColor; |
|
2773 SetupContext(); |
|
2774 gfx->NewPath(); |
|
2775 } |
|
2776 |
|
2777 void |
|
2778 SVGTextDrawPathCallbacks::NotifyGlyphPathEmitted() |
|
2779 { |
|
2780 HandleTextGeometry(); |
|
2781 gfx->NewPath(); |
|
2782 } |
|
2783 |
|
2784 void |
|
2785 SVGTextDrawPathCallbacks::NotifyBeforeSVGGlyphPainted() |
|
2786 { |
|
2787 gfx->Save(); |
|
2788 } |
|
2789 |
|
2790 void |
|
2791 SVGTextDrawPathCallbacks::NotifyAfterSVGGlyphPainted() |
|
2792 { |
|
2793 gfx->Restore(); |
|
2794 gfx->NewPath(); |
|
2795 } |
|
2796 |
|
2797 void |
|
2798 SVGTextDrawPathCallbacks::NotifyAfterText() |
|
2799 { |
|
2800 gfx->Restore(); |
|
2801 } |
|
2802 |
|
2803 void |
|
2804 SVGTextDrawPathCallbacks::NotifyBeforeSelectionBackground(nscolor aColor) |
|
2805 { |
|
2806 if (mRenderMode != SVGAutoRenderState::NORMAL) { |
|
2807 // Don't paint selection backgrounds when in a clip path. |
|
2808 return; |
|
2809 } |
|
2810 |
|
2811 mColor = aColor; |
|
2812 gfx->Save(); |
|
2813 } |
|
2814 |
|
2815 void |
|
2816 SVGTextDrawPathCallbacks::NotifySelectionBackgroundPathEmitted() |
|
2817 { |
|
2818 if (mRenderMode != SVGAutoRenderState::NORMAL) { |
|
2819 // Don't paint selection backgrounds when in a clip path. |
|
2820 return; |
|
2821 } |
|
2822 |
|
2823 if (SetFillColor()) { |
|
2824 FillWithOpacity(); |
|
2825 } |
|
2826 gfx->Restore(); |
|
2827 } |
|
2828 |
|
2829 void |
|
2830 SVGTextDrawPathCallbacks::NotifyBeforeDecorationLine(nscolor aColor) |
|
2831 { |
|
2832 mColor = aColor; |
|
2833 SetupContext(); |
|
2834 } |
|
2835 |
|
2836 void |
|
2837 SVGTextDrawPathCallbacks::NotifyDecorationLinePathEmitted() |
|
2838 { |
|
2839 HandleTextGeometry(); |
|
2840 gfx->NewPath(); |
|
2841 gfx->Restore(); |
|
2842 } |
|
2843 |
|
2844 void |
|
2845 SVGTextDrawPathCallbacks::NotifyBeforeSelectionDecorationLine(nscolor aColor) |
|
2846 { |
|
2847 if (mRenderMode != SVGAutoRenderState::NORMAL) { |
|
2848 // Don't paint selection decorations when in a clip path. |
|
2849 return; |
|
2850 } |
|
2851 |
|
2852 mColor = aColor; |
|
2853 gfx->Save(); |
|
2854 } |
|
2855 |
|
2856 void |
|
2857 SVGTextDrawPathCallbacks::NotifySelectionDecorationLinePathEmitted() |
|
2858 { |
|
2859 if (mRenderMode != SVGAutoRenderState::NORMAL) { |
|
2860 // Don't paint selection decorations when in a clip path. |
|
2861 return; |
|
2862 } |
|
2863 |
|
2864 FillAndStrokeGeometry(); |
|
2865 gfx->Restore(); |
|
2866 } |
|
2867 |
|
2868 void |
|
2869 SVGTextDrawPathCallbacks::FillWithOpacity() |
|
2870 { |
|
2871 gfx->FillWithOpacity(mColor == NS_40PERCENT_FOREGROUND_COLOR ? 0.4 : 1.0); |
|
2872 } |
|
2873 |
|
2874 void |
|
2875 SVGTextDrawPathCallbacks::SetupContext() |
|
2876 { |
|
2877 gfx->Save(); |
|
2878 |
|
2879 // XXX This is copied from nsSVGGlyphFrame::Render, but cairo doesn't actually |
|
2880 // seem to do anything with the antialias mode. So we can perhaps remove it, |
|
2881 // or make SetAntialiasMode set cairo text antialiasing too. |
|
2882 switch (mFrame->StyleSVG()->mTextRendering) { |
|
2883 case NS_STYLE_TEXT_RENDERING_OPTIMIZESPEED: |
|
2884 gfx->SetAntialiasMode(gfxContext::MODE_ALIASED); |
|
2885 break; |
|
2886 default: |
|
2887 gfx->SetAntialiasMode(gfxContext::MODE_COVERAGE); |
|
2888 break; |
|
2889 } |
|
2890 } |
|
2891 |
|
2892 void |
|
2893 SVGTextDrawPathCallbacks::HandleTextGeometry() |
|
2894 { |
|
2895 if (mRenderMode != SVGAutoRenderState::NORMAL) { |
|
2896 // We're in a clip path. |
|
2897 if (mRenderMode == SVGAutoRenderState::CLIP_MASK) { |
|
2898 gfx->SetColor(gfxRGBA(1.0f, 1.0f, 1.0f, 1.0f)); |
|
2899 gfx->Fill(); |
|
2900 } |
|
2901 } else { |
|
2902 // Normal painting. |
|
2903 gfxContextMatrixAutoSaveRestore saveMatrix(gfx); |
|
2904 gfx->SetMatrix(mCanvasTM); |
|
2905 |
|
2906 FillAndStrokeGeometry(); |
|
2907 } |
|
2908 } |
|
2909 |
|
2910 bool |
|
2911 SVGTextDrawPathCallbacks::SetFillColor() |
|
2912 { |
|
2913 if (mColor == NS_SAME_AS_FOREGROUND_COLOR || |
|
2914 mColor == NS_40PERCENT_FOREGROUND_COLOR) { |
|
2915 return nsSVGUtils::SetupCairoFillPaint(mFrame, gfx); |
|
2916 } |
|
2917 |
|
2918 if (mColor == NS_TRANSPARENT) { |
|
2919 return false; |
|
2920 } |
|
2921 |
|
2922 gfx->SetColor(gfxRGBA(mColor)); |
|
2923 return true; |
|
2924 } |
|
2925 |
|
2926 void |
|
2927 SVGTextDrawPathCallbacks::FillAndStrokeGeometry() |
|
2928 { |
|
2929 bool pushedGroup = false; |
|
2930 if (mColor == NS_40PERCENT_FOREGROUND_COLOR) { |
|
2931 pushedGroup = true; |
|
2932 gfx->PushGroup(gfxContentType::COLOR_ALPHA); |
|
2933 } |
|
2934 |
|
2935 uint32_t paintOrder = mFrame->StyleSVG()->mPaintOrder; |
|
2936 if (paintOrder == NS_STYLE_PAINT_ORDER_NORMAL) { |
|
2937 FillGeometry(); |
|
2938 StrokeGeometry(); |
|
2939 } else { |
|
2940 while (paintOrder) { |
|
2941 uint32_t component = |
|
2942 paintOrder & ((1 << NS_STYLE_PAINT_ORDER_BITWIDTH) - 1); |
|
2943 switch (component) { |
|
2944 case NS_STYLE_PAINT_ORDER_FILL: |
|
2945 FillGeometry(); |
|
2946 break; |
|
2947 case NS_STYLE_PAINT_ORDER_STROKE: |
|
2948 StrokeGeometry(); |
|
2949 break; |
|
2950 } |
|
2951 paintOrder >>= NS_STYLE_PAINT_ORDER_BITWIDTH; |
|
2952 } |
|
2953 } |
|
2954 |
|
2955 if (pushedGroup) { |
|
2956 gfx->PopGroupToSource(); |
|
2957 gfx->Paint(0.4); |
|
2958 } |
|
2959 } |
|
2960 |
|
2961 void |
|
2962 SVGTextDrawPathCallbacks::FillGeometry() |
|
2963 { |
|
2964 if (SetFillColor()) { |
|
2965 gfx->Fill(); |
|
2966 } |
|
2967 } |
|
2968 |
|
2969 void |
|
2970 SVGTextDrawPathCallbacks::StrokeGeometry() |
|
2971 { |
|
2972 if (mColor == NS_SAME_AS_FOREGROUND_COLOR || |
|
2973 mColor == NS_40PERCENT_FOREGROUND_COLOR) { |
|
2974 // Don't paint the stroke when we are filling with a selection color. |
|
2975 if (nsSVGUtils::SetupCairoStroke(mFrame, gfx)) { |
|
2976 gfx->Stroke(); |
|
2977 } |
|
2978 } |
|
2979 } |
|
2980 |
|
2981 //---------------------------------------------------------------------- |
|
2982 // SVGTextContextPaint methods: |
|
2983 |
|
2984 already_AddRefed<gfxPattern> |
|
2985 SVGTextContextPaint::GetFillPattern(float aOpacity, |
|
2986 const gfxMatrix& aCTM) |
|
2987 { |
|
2988 return mFillPaint.GetPattern(aOpacity, &nsStyleSVG::mFill, aCTM); |
|
2989 } |
|
2990 |
|
2991 already_AddRefed<gfxPattern> |
|
2992 SVGTextContextPaint::GetStrokePattern(float aOpacity, |
|
2993 const gfxMatrix& aCTM) |
|
2994 { |
|
2995 return mStrokePaint.GetPattern(aOpacity, &nsStyleSVG::mStroke, aCTM); |
|
2996 } |
|
2997 |
|
2998 already_AddRefed<gfxPattern> |
|
2999 SVGTextContextPaint::Paint::GetPattern(float aOpacity, |
|
3000 nsStyleSVGPaint nsStyleSVG::*aFillOrStroke, |
|
3001 const gfxMatrix& aCTM) |
|
3002 { |
|
3003 nsRefPtr<gfxPattern> pattern; |
|
3004 if (mPatternCache.Get(aOpacity, getter_AddRefs(pattern))) { |
|
3005 // Set the pattern matrix just in case it was messed with by a previous |
|
3006 // caller. We should get the same matrix each time a pattern is constructed |
|
3007 // so this should be fine. |
|
3008 pattern->SetMatrix(aCTM * mPatternMatrix); |
|
3009 return pattern.forget(); |
|
3010 } |
|
3011 |
|
3012 switch (mPaintType) { |
|
3013 case eStyleSVGPaintType_None: |
|
3014 pattern = new gfxPattern(gfxRGBA(0.0f, 0.0f, 0.0f, 0.0f)); |
|
3015 mPatternMatrix = gfxMatrix(); |
|
3016 break; |
|
3017 case eStyleSVGPaintType_Color: |
|
3018 pattern = new gfxPattern(gfxRGBA(NS_GET_R(mPaintDefinition.mColor) / 255.0, |
|
3019 NS_GET_G(mPaintDefinition.mColor) / 255.0, |
|
3020 NS_GET_B(mPaintDefinition.mColor) / 255.0, |
|
3021 NS_GET_A(mPaintDefinition.mColor) / 255.0 * aOpacity)); |
|
3022 mPatternMatrix = gfxMatrix(); |
|
3023 break; |
|
3024 case eStyleSVGPaintType_Server: |
|
3025 pattern = mPaintDefinition.mPaintServerFrame->GetPaintServerPattern(mFrame, |
|
3026 mContextMatrix, |
|
3027 aFillOrStroke, |
|
3028 aOpacity); |
|
3029 { |
|
3030 // m maps original-user-space to pattern space |
|
3031 gfxMatrix m = pattern->GetMatrix(); |
|
3032 gfxMatrix deviceToOriginalUserSpace = mContextMatrix; |
|
3033 deviceToOriginalUserSpace.Invert(); |
|
3034 // mPatternMatrix maps device space to pattern space via original user space |
|
3035 mPatternMatrix = deviceToOriginalUserSpace * m; |
|
3036 } |
|
3037 pattern->SetMatrix(aCTM * mPatternMatrix); |
|
3038 break; |
|
3039 case eStyleSVGPaintType_ContextFill: |
|
3040 pattern = mPaintDefinition.mContextPaint->GetFillPattern(aOpacity, aCTM); |
|
3041 // Don't cache this. mContextPaint will have cached it anyway. If we |
|
3042 // cache it, we'll have to compute mPatternMatrix, which is annoying. |
|
3043 return pattern.forget(); |
|
3044 case eStyleSVGPaintType_ContextStroke: |
|
3045 pattern = mPaintDefinition.mContextPaint->GetStrokePattern(aOpacity, aCTM); |
|
3046 // Don't cache this. mContextPaint will have cached it anyway. If we |
|
3047 // cache it, we'll have to compute mPatternMatrix, which is annoying. |
|
3048 return pattern.forget(); |
|
3049 default: |
|
3050 MOZ_ASSERT(false, "invalid paint type"); |
|
3051 return nullptr; |
|
3052 } |
|
3053 |
|
3054 mPatternCache.Put(aOpacity, pattern); |
|
3055 return pattern.forget(); |
|
3056 } |
|
3057 |
|
3058 } // namespace mozilla |
|
3059 |
|
3060 |
|
3061 // ============================================================================ |
|
3062 // SVGTextFrame |
|
3063 |
|
3064 // ---------------------------------------------------------------------------- |
|
3065 // Display list item |
|
3066 |
|
3067 class nsDisplaySVGText : public nsDisplayItem { |
|
3068 public: |
|
3069 nsDisplaySVGText(nsDisplayListBuilder* aBuilder, |
|
3070 SVGTextFrame* aFrame) |
|
3071 : nsDisplayItem(aBuilder, aFrame), |
|
3072 mDisableSubpixelAA(false) |
|
3073 { |
|
3074 MOZ_COUNT_CTOR(nsDisplaySVGText); |
|
3075 NS_ABORT_IF_FALSE(aFrame, "Must have a frame!"); |
|
3076 } |
|
3077 #ifdef NS_BUILD_REFCNT_LOGGING |
|
3078 virtual ~nsDisplaySVGText() { |
|
3079 MOZ_COUNT_DTOR(nsDisplaySVGText); |
|
3080 } |
|
3081 #endif |
|
3082 |
|
3083 NS_DISPLAY_DECL_NAME("nsDisplaySVGText", TYPE_SVG_TEXT) |
|
3084 |
|
3085 virtual void DisableComponentAlpha() MOZ_OVERRIDE { |
|
3086 mDisableSubpixelAA = true; |
|
3087 } |
|
3088 virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, |
|
3089 HitTestState* aState, |
|
3090 nsTArray<nsIFrame*> *aOutFrames) MOZ_OVERRIDE; |
|
3091 virtual void Paint(nsDisplayListBuilder* aBuilder, |
|
3092 nsRenderingContext* aCtx) MOZ_OVERRIDE; |
|
3093 virtual nsRect GetComponentAlphaBounds(nsDisplayListBuilder* aBuilder) MOZ_OVERRIDE { |
|
3094 bool snap; |
|
3095 return GetBounds(aBuilder, &snap); |
|
3096 } |
|
3097 private: |
|
3098 bool mDisableSubpixelAA; |
|
3099 }; |
|
3100 |
|
3101 void |
|
3102 nsDisplaySVGText::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, |
|
3103 HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) |
|
3104 { |
|
3105 SVGTextFrame *frame = static_cast<SVGTextFrame*>(mFrame); |
|
3106 nsPoint pointRelativeToReferenceFrame = aRect.Center(); |
|
3107 // ToReferenceFrame() includes frame->GetPosition(), our user space position. |
|
3108 nsPoint userSpacePt = pointRelativeToReferenceFrame - |
|
3109 (ToReferenceFrame() - frame->GetPosition()); |
|
3110 |
|
3111 nsIFrame* target = frame->GetFrameForPoint(userSpacePt); |
|
3112 if (target) { |
|
3113 aOutFrames->AppendElement(target); |
|
3114 } |
|
3115 } |
|
3116 |
|
3117 void |
|
3118 nsDisplaySVGText::Paint(nsDisplayListBuilder* aBuilder, |
|
3119 nsRenderingContext* aCtx) |
|
3120 { |
|
3121 gfxContextAutoDisableSubpixelAntialiasing |
|
3122 disable(aCtx->ThebesContext(), mDisableSubpixelAA); |
|
3123 |
|
3124 // ToReferenceFrame includes our mRect offset, but painting takes |
|
3125 // account of that too. To avoid double counting, we subtract that |
|
3126 // here. |
|
3127 nsPoint offset = ToReferenceFrame() - mFrame->GetPosition(); |
|
3128 |
|
3129 aCtx->PushState(); |
|
3130 aCtx->Translate(offset); |
|
3131 static_cast<SVGTextFrame*>(mFrame)->PaintSVG(aCtx, nullptr); |
|
3132 aCtx->PopState(); |
|
3133 } |
|
3134 |
|
3135 // --------------------------------------------------------------------- |
|
3136 // nsQueryFrame methods |
|
3137 |
|
3138 NS_QUERYFRAME_HEAD(SVGTextFrame) |
|
3139 NS_QUERYFRAME_ENTRY(SVGTextFrame) |
|
3140 NS_QUERYFRAME_TAIL_INHERITING(SVGTextFrameBase) |
|
3141 |
|
3142 // --------------------------------------------------------------------- |
|
3143 // Implementation |
|
3144 |
|
3145 nsIFrame* |
|
3146 NS_NewSVGTextFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) |
|
3147 { |
|
3148 return new (aPresShell) SVGTextFrame(aContext); |
|
3149 } |
|
3150 |
|
3151 NS_IMPL_FRAMEARENA_HELPERS(SVGTextFrame) |
|
3152 |
|
3153 // --------------------------------------------------------------------- |
|
3154 // nsIFrame methods |
|
3155 |
|
3156 void |
|
3157 SVGTextFrame::Init(nsIContent* aContent, |
|
3158 nsIFrame* aParent, |
|
3159 nsIFrame* aPrevInFlow) |
|
3160 { |
|
3161 NS_ASSERTION(aContent->IsSVG(nsGkAtoms::text), "Content is not an SVG text"); |
|
3162 |
|
3163 SVGTextFrameBase::Init(aContent, aParent, aPrevInFlow); |
|
3164 AddStateBits((aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) | |
|
3165 NS_FRAME_SVG_LAYOUT | NS_FRAME_IS_SVG_TEXT); |
|
3166 |
|
3167 mMutationObserver.StartObserving(this); |
|
3168 } |
|
3169 |
|
3170 void |
|
3171 SVGTextFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, |
|
3172 const nsRect& aDirtyRect, |
|
3173 const nsDisplayListSet& aLists) |
|
3174 { |
|
3175 if (NS_SUBTREE_DIRTY(this)) { |
|
3176 // We can sometimes be asked to paint before reflow happens and we |
|
3177 // have updated mPositions, etc. In this case, we just avoid |
|
3178 // painting. |
|
3179 return; |
|
3180 } |
|
3181 aLists.Content()->AppendNewToTop( |
|
3182 new (aBuilder) nsDisplaySVGText(aBuilder, this)); |
|
3183 } |
|
3184 |
|
3185 nsresult |
|
3186 SVGTextFrame::AttributeChanged(int32_t aNameSpaceID, |
|
3187 nsIAtom* aAttribute, |
|
3188 int32_t aModType) |
|
3189 { |
|
3190 if (aNameSpaceID != kNameSpaceID_None) |
|
3191 return NS_OK; |
|
3192 |
|
3193 if (aAttribute == nsGkAtoms::transform) { |
|
3194 // We don't invalidate for transform changes (the layers code does that). |
|
3195 // Also note that SVGTransformableElement::GetAttributeChangeHint will |
|
3196 // return nsChangeHint_UpdateOverflow for "transform" attribute changes |
|
3197 // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call. |
|
3198 |
|
3199 if (!(mState & NS_FRAME_FIRST_REFLOW) && |
|
3200 mCanvasTM && mCanvasTM->IsSingular()) { |
|
3201 // We won't have calculated the glyph positions correctly. |
|
3202 NotifyGlyphMetricsChange(); |
|
3203 } |
|
3204 mCanvasTM = nullptr; |
|
3205 } else if (IsGlyphPositioningAttribute(aAttribute) || |
|
3206 aAttribute == nsGkAtoms::textLength || |
|
3207 aAttribute == nsGkAtoms::lengthAdjust) { |
|
3208 NotifyGlyphMetricsChange(); |
|
3209 } |
|
3210 |
|
3211 return NS_OK; |
|
3212 } |
|
3213 |
|
3214 nsIAtom * |
|
3215 SVGTextFrame::GetType() const |
|
3216 { |
|
3217 return nsGkAtoms::svgTextFrame; |
|
3218 } |
|
3219 |
|
3220 void |
|
3221 SVGTextFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) |
|
3222 { |
|
3223 if (mState & NS_FRAME_IS_NONDISPLAY) { |
|
3224 // We need this DidSetStyleContext override to handle cases like this: |
|
3225 // |
|
3226 // <defs> |
|
3227 // <g> |
|
3228 // <mask> |
|
3229 // <text>...</text> |
|
3230 // </mask> |
|
3231 // </g> |
|
3232 // </defs> |
|
3233 // |
|
3234 // where the <text> is non-display, and a style change occurs on the <defs>, |
|
3235 // the <g>, the <mask>, or the <text> itself. If the style change happened |
|
3236 // on the parent of the <defs>, then in |
|
3237 // nsSVGDisplayContainerFrame::ReflowSVG, we would find the non-display |
|
3238 // <defs> container and then call ReflowSVGNonDisplayText on it. If we do |
|
3239 // not actually reflow the parent of the <defs>, then without this |
|
3240 // DidSetStyleContext we would (a) not cause the <text>'s anonymous block |
|
3241 // child to be reflowed when it is next painted, and (b) not cause the |
|
3242 // <text> to be repainted anyway since the user of the <mask> would not |
|
3243 // know it needs to be repainted. |
|
3244 ScheduleReflowSVGNonDisplayText(); |
|
3245 } |
|
3246 } |
|
3247 |
|
3248 void |
|
3249 SVGTextFrame::ReflowSVGNonDisplayText() |
|
3250 { |
|
3251 MOZ_ASSERT(nsSVGUtils::AnyOuterSVGIsCallingReflowSVG(this), |
|
3252 "only call ReflowSVGNonDisplayText when an outer SVG frame is " |
|
3253 "under ReflowSVG"); |
|
3254 MOZ_ASSERT(mState & NS_FRAME_IS_NONDISPLAY, |
|
3255 "only call ReflowSVGNonDisplayText if the frame is " |
|
3256 "NS_FRAME_IS_NONDISPLAY"); |
|
3257 |
|
3258 // We had a style change, so we mark this frame as dirty so that the next |
|
3259 // time it is painted, we reflow the anonymous block frame. |
|
3260 AddStateBits(NS_FRAME_IS_DIRTY); |
|
3261 |
|
3262 // We also need to call InvalidateRenderingObservers, so that if the <text> |
|
3263 // element is within a <mask>, say, the element referencing the <mask> will |
|
3264 // be updated, which will then cause this SVGTextFrame to be painted and |
|
3265 // in doing so cause the anonymous block frame to be reflowed. |
|
3266 nsSVGEffects::InvalidateRenderingObservers(this); |
|
3267 |
|
3268 // Finally, we need to actually reflow the anonymous block frame and update |
|
3269 // mPositions, in case we are being reflowed immediately after a DOM |
|
3270 // mutation that needs frame reconstruction. |
|
3271 MaybeReflowAnonymousBlockChild(); |
|
3272 UpdateGlyphPositioning(); |
|
3273 } |
|
3274 |
|
3275 void |
|
3276 SVGTextFrame::ScheduleReflowSVGNonDisplayText() |
|
3277 { |
|
3278 MOZ_ASSERT(!nsSVGUtils::OuterSVGIsCallingReflowSVG(this), |
|
3279 "do not call ScheduleReflowSVGNonDisplayText when the outer SVG " |
|
3280 "frame is under ReflowSVG"); |
|
3281 MOZ_ASSERT(!(mState & NS_STATE_SVG_TEXT_IN_REFLOW), |
|
3282 "do not call ScheduleReflowSVGNonDisplayText while reflowing the " |
|
3283 "anonymous block child"); |
|
3284 |
|
3285 // We need to find an ancestor frame that we can call FrameNeedsReflow |
|
3286 // on that will cause the document to be marked as needing relayout, |
|
3287 // and for that ancestor (or some further ancestor) to be marked as |
|
3288 // a root to reflow. We choose the closest ancestor frame that is not |
|
3289 // NS_FRAME_IS_NONDISPLAY and which is either an outer SVG frame or a |
|
3290 // non-SVG frame. (We don't consider displayed SVG frame ancestors toerh |
|
3291 // than nsSVGOuterSVGFrame, since calling FrameNeedsReflow on those other |
|
3292 // SVG frames would do a bunch of unnecessary work on the SVG frames up to |
|
3293 // the nsSVGOuterSVGFrame.) |
|
3294 |
|
3295 nsIFrame* f = this; |
|
3296 while (f) { |
|
3297 if (!(f->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) { |
|
3298 if (NS_SUBTREE_DIRTY(f)) { |
|
3299 // This is a displayed frame, so if it is already dirty, we will be reflowed |
|
3300 // soon anyway. No need to call FrameNeedsReflow again, then. |
|
3301 return; |
|
3302 } |
|
3303 if (!f->IsFrameOfType(eSVG) || |
|
3304 (f->GetStateBits() & NS_STATE_IS_OUTER_SVG)) { |
|
3305 break; |
|
3306 } |
|
3307 f->AddStateBits(NS_FRAME_HAS_DIRTY_CHILDREN); |
|
3308 } |
|
3309 f = f->GetParent(); |
|
3310 } |
|
3311 |
|
3312 MOZ_ASSERT(f, "should have found an ancestor frame to reflow"); |
|
3313 |
|
3314 PresContext()->PresShell()->FrameNeedsReflow( |
|
3315 f, nsIPresShell::eStyleChange, NS_FRAME_IS_DIRTY); |
|
3316 } |
|
3317 |
|
3318 NS_IMPL_ISUPPORTS(SVGTextFrame::MutationObserver, nsIMutationObserver) |
|
3319 |
|
3320 void |
|
3321 SVGTextFrame::MutationObserver::ContentAppended(nsIDocument* aDocument, |
|
3322 nsIContent* aContainer, |
|
3323 nsIContent* aFirstNewContent, |
|
3324 int32_t aNewIndexInContainer) |
|
3325 { |
|
3326 mFrame->NotifyGlyphMetricsChange(); |
|
3327 } |
|
3328 |
|
3329 void |
|
3330 SVGTextFrame::MutationObserver::ContentInserted( |
|
3331 nsIDocument* aDocument, |
|
3332 nsIContent* aContainer, |
|
3333 nsIContent* aChild, |
|
3334 int32_t aIndexInContainer) |
|
3335 { |
|
3336 mFrame->NotifyGlyphMetricsChange(); |
|
3337 } |
|
3338 |
|
3339 void |
|
3340 SVGTextFrame::MutationObserver::ContentRemoved( |
|
3341 nsIDocument *aDocument, |
|
3342 nsIContent* aContainer, |
|
3343 nsIContent* aChild, |
|
3344 int32_t aIndexInContainer, |
|
3345 nsIContent* aPreviousSibling) |
|
3346 { |
|
3347 mFrame->NotifyGlyphMetricsChange(); |
|
3348 } |
|
3349 |
|
3350 void |
|
3351 SVGTextFrame::MutationObserver::CharacterDataChanged( |
|
3352 nsIDocument* aDocument, |
|
3353 nsIContent* aContent, |
|
3354 CharacterDataChangeInfo* aInfo) |
|
3355 { |
|
3356 mFrame->NotifyGlyphMetricsChange(); |
|
3357 } |
|
3358 |
|
3359 void |
|
3360 SVGTextFrame::MutationObserver::AttributeChanged( |
|
3361 nsIDocument* aDocument, |
|
3362 mozilla::dom::Element* aElement, |
|
3363 int32_t aNameSpaceID, |
|
3364 nsIAtom* aAttribute, |
|
3365 int32_t aModType) |
|
3366 { |
|
3367 if (!aElement->IsSVG()) { |
|
3368 return; |
|
3369 } |
|
3370 |
|
3371 // Attribute changes on this element are handled in |
|
3372 // SVGTextFrame::AttributeChanged. |
|
3373 if (aElement == mFrame->GetContent()) { |
|
3374 return; |
|
3375 } |
|
3376 |
|
3377 // Attributes changes on descendent elements. |
|
3378 if (aElement->Tag() == nsGkAtoms::textPath) { |
|
3379 if (aNameSpaceID == kNameSpaceID_None && |
|
3380 aAttribute == nsGkAtoms::startOffset) { |
|
3381 mFrame->NotifyGlyphMetricsChange(); |
|
3382 } else if (aNameSpaceID == kNameSpaceID_XLink && |
|
3383 aAttribute == nsGkAtoms::href) { |
|
3384 // Blow away our reference, if any |
|
3385 nsIFrame* childElementFrame = aElement->GetPrimaryFrame(); |
|
3386 if (childElementFrame) { |
|
3387 childElementFrame->Properties().Delete(nsSVGEffects::HrefProperty()); |
|
3388 mFrame->NotifyGlyphMetricsChange(); |
|
3389 } |
|
3390 } |
|
3391 } else { |
|
3392 if (aNameSpaceID == kNameSpaceID_None && |
|
3393 IsGlyphPositioningAttribute(aAttribute)) { |
|
3394 mFrame->NotifyGlyphMetricsChange(); |
|
3395 } |
|
3396 } |
|
3397 } |
|
3398 |
|
3399 void |
|
3400 SVGTextFrame::FindCloserFrameForSelection( |
|
3401 nsPoint aPoint, |
|
3402 nsIFrame::FrameWithDistance* aCurrentBestFrame) |
|
3403 { |
|
3404 if (GetStateBits() & NS_FRAME_IS_NONDISPLAY) { |
|
3405 return; |
|
3406 } |
|
3407 |
|
3408 UpdateGlyphPositioning(); |
|
3409 |
|
3410 nsPresContext* presContext = PresContext(); |
|
3411 |
|
3412 // Find the frame that has the closest rendered run rect to aPoint. |
|
3413 TextRenderedRunIterator it(this); |
|
3414 for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { |
|
3415 uint32_t flags = TextRenderedRun::eIncludeFill | |
|
3416 TextRenderedRun::eIncludeStroke | |
|
3417 TextRenderedRun::eNoHorizontalOverflow; |
|
3418 SVGBBox userRect = run.GetUserSpaceRect(presContext, flags); |
|
3419 if (!userRect.IsEmpty()) { |
|
3420 nsRect rect = nsSVGUtils::ToCanvasBounds(userRect.ToThebesRect(), |
|
3421 GetCanvasTM(FOR_HIT_TESTING), |
|
3422 presContext); |
|
3423 |
|
3424 if (nsLayoutUtils::PointIsCloserToRect(aPoint, rect, |
|
3425 aCurrentBestFrame->mXDistance, |
|
3426 aCurrentBestFrame->mYDistance)) { |
|
3427 aCurrentBestFrame->mFrame = run.mFrame; |
|
3428 } |
|
3429 } |
|
3430 } |
|
3431 } |
|
3432 |
|
3433 //---------------------------------------------------------------------- |
|
3434 // nsISVGChildFrame methods |
|
3435 |
|
3436 void |
|
3437 SVGTextFrame::NotifySVGChanged(uint32_t aFlags) |
|
3438 { |
|
3439 NS_ABORT_IF_FALSE(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED), |
|
3440 "Invalidation logic may need adjusting"); |
|
3441 |
|
3442 bool needNewBounds = false; |
|
3443 bool needGlyphMetricsUpdate = false; |
|
3444 bool needNewCanvasTM = false; |
|
3445 |
|
3446 if ((aFlags & COORD_CONTEXT_CHANGED) && |
|
3447 (mState & NS_STATE_SVG_POSITIONING_MAY_USE_PERCENTAGES)) { |
|
3448 needGlyphMetricsUpdate = true; |
|
3449 } |
|
3450 |
|
3451 if (aFlags & TRANSFORM_CHANGED) { |
|
3452 needNewCanvasTM = true; |
|
3453 if (mCanvasTM && mCanvasTM->IsSingular()) { |
|
3454 // We won't have calculated the glyph positions correctly. |
|
3455 needNewBounds = true; |
|
3456 needGlyphMetricsUpdate = true; |
|
3457 } |
|
3458 if (StyleSVGReset()->mVectorEffect == |
|
3459 NS_STYLE_VECTOR_EFFECT_NON_SCALING_STROKE) { |
|
3460 // Stroke currently contributes to our mRect, and our stroke depends on |
|
3461 // the transform to our outer-<svg> if |vector-effect:non-scaling-stroke|. |
|
3462 needNewBounds = true; |
|
3463 } |
|
3464 } |
|
3465 |
|
3466 // If the scale at which we computed our mFontSizeScaleFactor has changed by |
|
3467 // at least a factor of two, reflow the text. This avoids reflowing text |
|
3468 // at every tick of a transform animation, but ensures our glyph metrics |
|
3469 // do not get too far out of sync with the final font size on the screen. |
|
3470 if (needNewCanvasTM && mLastContextScale != 0.0f) { |
|
3471 mCanvasTM = nullptr; |
|
3472 // If we are a non-display frame, then we don't want to call |
|
3473 // GetCanvasTM(FOR_OUTERSVG_TM), since the context scale does not use it. |
|
3474 gfxMatrix newTM = |
|
3475 (mState & NS_FRAME_IS_NONDISPLAY) ? gfxMatrix() : |
|
3476 GetCanvasTM(FOR_OUTERSVG_TM); |
|
3477 // Compare the old and new context scales. |
|
3478 float scale = GetContextScale(newTM); |
|
3479 float change = scale / mLastContextScale; |
|
3480 if (change >= 2.0f || change <= 0.5f) { |
|
3481 needNewBounds = true; |
|
3482 needGlyphMetricsUpdate = true; |
|
3483 } |
|
3484 } |
|
3485 |
|
3486 if (needNewBounds) { |
|
3487 // Ancestor changes can't affect how we render from the perspective of |
|
3488 // any rendering observers that we may have, so we don't need to |
|
3489 // invalidate them. We also don't need to invalidate ourself, since our |
|
3490 // changed ancestor will have invalidated its entire area, which includes |
|
3491 // our area. |
|
3492 ScheduleReflowSVG(); |
|
3493 } |
|
3494 |
|
3495 if (needGlyphMetricsUpdate) { |
|
3496 // If we are positioned using percentage values we need to update our |
|
3497 // position whenever our viewport's dimensions change. But only do this if |
|
3498 // we have been reflowed once, otherwise the glyph positioning will be |
|
3499 // wrong. (We need to wait until bidi reordering has been done.) |
|
3500 if (!(mState & NS_FRAME_FIRST_REFLOW)) { |
|
3501 NotifyGlyphMetricsChange(); |
|
3502 } |
|
3503 } |
|
3504 } |
|
3505 |
|
3506 /** |
|
3507 * Gets the offset into a DOM node that the specified caret is positioned at. |
|
3508 */ |
|
3509 static int32_t |
|
3510 GetCaretOffset(nsCaret* aCaret) |
|
3511 { |
|
3512 nsCOMPtr<nsISelection> selection = aCaret->GetCaretDOMSelection(); |
|
3513 if (!selection) { |
|
3514 return -1; |
|
3515 } |
|
3516 |
|
3517 int32_t offset = -1; |
|
3518 selection->GetAnchorOffset(&offset); |
|
3519 return offset; |
|
3520 } |
|
3521 |
|
3522 /** |
|
3523 * Returns whether the caret should be painted for a given TextRenderedRun |
|
3524 * by checking whether the caret is in the range covered by the rendered run. |
|
3525 * |
|
3526 * @param aThisRun The TextRenderedRun to be painted. |
|
3527 * @param aCaret The caret. |
|
3528 */ |
|
3529 static bool |
|
3530 ShouldPaintCaret(const TextRenderedRun& aThisRun, nsCaret* aCaret) |
|
3531 { |
|
3532 int32_t caretOffset = GetCaretOffset(aCaret); |
|
3533 |
|
3534 if (caretOffset < 0) { |
|
3535 return false; |
|
3536 } |
|
3537 |
|
3538 if (uint32_t(caretOffset) >= aThisRun.mTextFrameContentOffset && |
|
3539 uint32_t(caretOffset) < aThisRun.mTextFrameContentOffset + |
|
3540 aThisRun.mTextFrameContentLength) { |
|
3541 return true; |
|
3542 } |
|
3543 |
|
3544 return false; |
|
3545 } |
|
3546 |
|
3547 nsresult |
|
3548 SVGTextFrame::PaintSVG(nsRenderingContext* aContext, |
|
3549 const nsIntRect *aDirtyRect, |
|
3550 nsIFrame* aTransformRoot) |
|
3551 { |
|
3552 nsIFrame* kid = GetFirstPrincipalChild(); |
|
3553 if (!kid) |
|
3554 return NS_OK; |
|
3555 |
|
3556 nsPresContext* presContext = PresContext(); |
|
3557 |
|
3558 gfxContext *gfx = aContext->ThebesContext(); |
|
3559 gfxMatrix initialMatrix = gfx->CurrentMatrix(); |
|
3560 |
|
3561 if (mState & NS_FRAME_IS_NONDISPLAY) { |
|
3562 // If we are in a canvas DrawWindow call that used the |
|
3563 // DRAWWINDOW_DO_NOT_FLUSH flag, then we may still have out |
|
3564 // of date frames. Just don't paint anything if they are |
|
3565 // dirty. |
|
3566 if (presContext->PresShell()->InDrawWindowNotFlushing() && |
|
3567 NS_SUBTREE_DIRTY(this)) { |
|
3568 return NS_OK; |
|
3569 } |
|
3570 // Text frames inside <clipPath>, <mask>, etc. will never have had |
|
3571 // ReflowSVG called on them, so call UpdateGlyphPositioning to do this now. |
|
3572 UpdateGlyphPositioning(); |
|
3573 } else if (NS_SUBTREE_DIRTY(this)) { |
|
3574 // If we are asked to paint before reflow has recomputed mPositions etc. |
|
3575 // directly via PaintSVG, rather than via a display list, then we need |
|
3576 // to bail out here too. |
|
3577 return NS_OK; |
|
3578 } |
|
3579 |
|
3580 gfxMatrix canvasTM = GetCanvasTM(FOR_PAINTING, aTransformRoot); |
|
3581 if (canvasTM.IsSingular()) { |
|
3582 NS_WARNING("Can't render text element!"); |
|
3583 return NS_ERROR_FAILURE; |
|
3584 } |
|
3585 |
|
3586 gfxMatrix matrixForPaintServers(canvasTM); |
|
3587 matrixForPaintServers.Multiply(initialMatrix); |
|
3588 |
|
3589 // Check if we need to draw anything. |
|
3590 if (aDirtyRect) { |
|
3591 NS_ASSERTION(!NS_SVGDisplayListPaintingEnabled() || |
|
3592 (mState & NS_FRAME_IS_NONDISPLAY), |
|
3593 "Display lists handle dirty rect intersection test"); |
|
3594 nsRect dirtyRect(aDirtyRect->x, aDirtyRect->y, |
|
3595 aDirtyRect->width, aDirtyRect->height); |
|
3596 |
|
3597 gfxFloat appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel(); |
|
3598 gfxRect frameRect(mRect.x / appUnitsPerDevPixel, |
|
3599 mRect.y / appUnitsPerDevPixel, |
|
3600 mRect.width / appUnitsPerDevPixel, |
|
3601 mRect.height / appUnitsPerDevPixel); |
|
3602 |
|
3603 nsRect canvasRect = nsLayoutUtils::RoundGfxRectToAppRect( |
|
3604 GetCanvasTM(FOR_OUTERSVG_TM).TransformBounds(frameRect), 1); |
|
3605 if (!canvasRect.Intersects(dirtyRect)) { |
|
3606 return NS_OK; |
|
3607 } |
|
3608 } |
|
3609 |
|
3610 // SVG paints in CSS px, but normally frames paint in dev pixels. Here we |
|
3611 // multiply a CSS-px-to-dev-pixel factor onto canvasTM so our children paint |
|
3612 // correctly. |
|
3613 float cssPxPerDevPx = presContext-> |
|
3614 AppUnitsToFloatCSSPixels(presContext->AppUnitsPerDevPixel()); |
|
3615 gfxMatrix canvasTMForChildren = canvasTM; |
|
3616 canvasTMForChildren.Scale(cssPxPerDevPx, cssPxPerDevPx); |
|
3617 initialMatrix.Scale(1 / cssPxPerDevPx, 1 / cssPxPerDevPx); |
|
3618 |
|
3619 gfxContextAutoSaveRestore save(gfx); |
|
3620 gfx->NewPath(); |
|
3621 gfx->Multiply(canvasTMForChildren); |
|
3622 gfxMatrix currentMatrix = gfx->CurrentMatrix(); |
|
3623 |
|
3624 nsRefPtr<nsCaret> caret = presContext->PresShell()->GetCaret(); |
|
3625 nsIFrame* caretFrame = caret->GetCaretFrame(); |
|
3626 |
|
3627 TextRenderedRunIterator it(this, TextRenderedRunIterator::eVisibleFrames); |
|
3628 TextRenderedRun run = it.Current(); |
|
3629 while (run.mFrame) { |
|
3630 nsTextFrame* frame = run.mFrame; |
|
3631 |
|
3632 // Determine how much of the left and right edges of the text frame we |
|
3633 // need to ignore. |
|
3634 SVGCharClipDisplayItem item(run); |
|
3635 |
|
3636 // Set up the fill and stroke so that SVG glyphs can get painted correctly |
|
3637 // when they use context-fill etc. |
|
3638 gfx->SetMatrix(initialMatrix); |
|
3639 gfxTextContextPaint *outerContextPaint = |
|
3640 (gfxTextContextPaint*)aContext->GetUserData(&gfxTextContextPaint::sUserDataKey); |
|
3641 |
|
3642 nsAutoPtr<gfxTextContextPaint> contextPaint; |
|
3643 DrawMode drawMode = |
|
3644 SetupCairoState(gfx, frame, outerContextPaint, |
|
3645 getter_Transfers(contextPaint)); |
|
3646 |
|
3647 // Set up the transform for painting the text frame for the substring |
|
3648 // indicated by the run. |
|
3649 gfxMatrix runTransform = |
|
3650 run.GetTransformFromUserSpaceForPainting(presContext, item); |
|
3651 runTransform.Multiply(currentMatrix); |
|
3652 gfx->SetMatrix(runTransform); |
|
3653 |
|
3654 if (drawMode != DrawMode(0)) { |
|
3655 nsRect frameRect = frame->GetVisualOverflowRect(); |
|
3656 bool paintSVGGlyphs; |
|
3657 if (ShouldRenderAsPath(aContext, frame, paintSVGGlyphs)) { |
|
3658 SVGTextDrawPathCallbacks callbacks(aContext, frame, |
|
3659 matrixForPaintServers, |
|
3660 paintSVGGlyphs); |
|
3661 frame->PaintText(aContext, nsPoint(), frameRect, item, |
|
3662 contextPaint, &callbacks); |
|
3663 } else { |
|
3664 frame->PaintText(aContext, nsPoint(), frameRect, item, |
|
3665 contextPaint, nullptr); |
|
3666 } |
|
3667 } |
|
3668 |
|
3669 if (frame == caretFrame && ShouldPaintCaret(run, caret)) { |
|
3670 // XXX Should we be looking at the fill/stroke colours to paint the |
|
3671 // caret with, rather than using the color property? |
|
3672 caret->PaintCaret(nullptr, aContext, frame, nsPoint()); |
|
3673 gfx->NewPath(); |
|
3674 } |
|
3675 |
|
3676 run = it.Next(); |
|
3677 } |
|
3678 |
|
3679 return NS_OK; |
|
3680 } |
|
3681 |
|
3682 nsIFrame* |
|
3683 SVGTextFrame::GetFrameForPoint(const nsPoint& aPoint) |
|
3684 { |
|
3685 NS_ASSERTION(GetFirstPrincipalChild(), "must have a child frame"); |
|
3686 |
|
3687 if (mState & NS_FRAME_IS_NONDISPLAY) { |
|
3688 // Text frames inside <clipPath> will never have had ReflowSVG called on |
|
3689 // them, so call UpdateGlyphPositioning to do this now. (Text frames |
|
3690 // inside <mask> and other non-display containers will never need to |
|
3691 // be hit tested.) |
|
3692 UpdateGlyphPositioning(); |
|
3693 } else { |
|
3694 NS_ASSERTION(!NS_SUBTREE_DIRTY(this), "reflow should have happened"); |
|
3695 } |
|
3696 |
|
3697 nsPresContext* presContext = PresContext(); |
|
3698 |
|
3699 gfxPoint pointInOuterSVGUserUnits = AppUnitsToGfxUnits(aPoint, presContext); |
|
3700 |
|
3701 TextRenderedRunIterator it(this); |
|
3702 nsIFrame* hit = nullptr; |
|
3703 for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { |
|
3704 uint16_t hitTestFlags = nsSVGUtils::GetGeometryHitTestFlags(run.mFrame); |
|
3705 if (!(hitTestFlags & (SVG_HIT_TEST_FILL | SVG_HIT_TEST_STROKE))) { |
|
3706 continue; |
|
3707 } |
|
3708 |
|
3709 gfxMatrix m = GetCanvasTM(FOR_HIT_TESTING); |
|
3710 m.PreMultiply(run.GetTransformFromRunUserSpaceToUserSpace(presContext)); |
|
3711 m.Invert(); |
|
3712 |
|
3713 gfxPoint pointInRunUserSpace = m.Transform(pointInOuterSVGUserUnits); |
|
3714 gfxRect frameRect = |
|
3715 run.GetRunUserSpaceRect(presContext, TextRenderedRun::eIncludeFill | |
|
3716 TextRenderedRun::eIncludeStroke).ToThebesRect(); |
|
3717 |
|
3718 if (Inside(frameRect, pointInRunUserSpace) && |
|
3719 nsSVGUtils::HitTestClip(this, aPoint)) { |
|
3720 hit = run.mFrame; |
|
3721 } |
|
3722 } |
|
3723 return hit; |
|
3724 } |
|
3725 |
|
3726 nsRect |
|
3727 SVGTextFrame::GetCoveredRegion() |
|
3728 { |
|
3729 return nsSVGUtils::TransformFrameRectToOuterSVG( |
|
3730 mRect, GetCanvasTM(FOR_OUTERSVG_TM), PresContext()); |
|
3731 } |
|
3732 |
|
3733 void |
|
3734 SVGTextFrame::ReflowSVG() |
|
3735 { |
|
3736 NS_ASSERTION(nsSVGUtils::OuterSVGIsCallingReflowSVG(this), |
|
3737 "This call is probaby a wasteful mistake"); |
|
3738 |
|
3739 NS_ABORT_IF_FALSE(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY), |
|
3740 "ReflowSVG mechanism not designed for this"); |
|
3741 |
|
3742 if (!nsSVGUtils::NeedsReflowSVG(this)) { |
|
3743 NS_ASSERTION(!(mState & NS_STATE_SVG_POSITIONING_DIRTY), "How did this happen?"); |
|
3744 return; |
|
3745 } |
|
3746 |
|
3747 MaybeReflowAnonymousBlockChild(); |
|
3748 UpdateGlyphPositioning(); |
|
3749 |
|
3750 nsPresContext* presContext = PresContext(); |
|
3751 |
|
3752 SVGBBox r; |
|
3753 TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames); |
|
3754 for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { |
|
3755 uint32_t runFlags = 0; |
|
3756 if (run.mFrame->StyleSVG()->mFill.mType != eStyleSVGPaintType_None) { |
|
3757 runFlags |= TextRenderedRun::eIncludeFill | |
|
3758 TextRenderedRun::eIncludeTextShadow; |
|
3759 } |
|
3760 if (nsSVGUtils::HasStroke(run.mFrame)) { |
|
3761 runFlags |= TextRenderedRun::eIncludeFill | |
|
3762 TextRenderedRun::eIncludeTextShadow; |
|
3763 } |
|
3764 // Our "visual" overflow rect needs to be valid for building display lists |
|
3765 // for hit testing, which means that for certain values of 'pointer-events' |
|
3766 // it needs to include the geometry of the fill or stroke even when the fill/ |
|
3767 // stroke don't actually render (e.g. when stroke="none" or |
|
3768 // stroke-opacity="0"). GetGeometryHitTestFlags accounts for 'pointer-events'. |
|
3769 // The text-shadow is not part of the hit-test area. |
|
3770 uint16_t hitTestFlags = nsSVGUtils::GetGeometryHitTestFlags(run.mFrame); |
|
3771 if (hitTestFlags & SVG_HIT_TEST_FILL) { |
|
3772 runFlags |= TextRenderedRun::eIncludeFill; |
|
3773 } |
|
3774 if (hitTestFlags & SVG_HIT_TEST_STROKE) { |
|
3775 runFlags |= TextRenderedRun::eIncludeStroke; |
|
3776 } |
|
3777 |
|
3778 if (runFlags) { |
|
3779 r.UnionEdges(run.GetUserSpaceRect(presContext, runFlags)); |
|
3780 } |
|
3781 } |
|
3782 |
|
3783 if (r.IsEmpty()) { |
|
3784 mRect.SetEmpty(); |
|
3785 } else { |
|
3786 mRect = |
|
3787 nsLayoutUtils::RoundGfxRectToAppRect(r.ToThebesRect(), presContext->AppUnitsPerCSSPixel()); |
|
3788 |
|
3789 // Due to rounding issues when we have a transform applied, we sometimes |
|
3790 // don't include an additional row of pixels. For now, just inflate our |
|
3791 // covered region. |
|
3792 mRect.Inflate(presContext->AppUnitsPerDevPixel()); |
|
3793 } |
|
3794 |
|
3795 if (mState & NS_FRAME_FIRST_REFLOW) { |
|
3796 // Make sure we have our filter property (if any) before calling |
|
3797 // FinishAndStoreOverflow (subsequent filter changes are handled off |
|
3798 // nsChangeHint_UpdateEffects): |
|
3799 nsSVGEffects::UpdateEffects(this); |
|
3800 } |
|
3801 |
|
3802 nsRect overflow = nsRect(nsPoint(0,0), mRect.Size()); |
|
3803 nsOverflowAreas overflowAreas(overflow, overflow); |
|
3804 FinishAndStoreOverflow(overflowAreas, mRect.Size()); |
|
3805 |
|
3806 // Now unset the various reflow bits: |
|
3807 mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY | |
|
3808 NS_FRAME_HAS_DIRTY_CHILDREN); |
|
3809 |
|
3810 // XXX nsSVGContainerFrame::ReflowSVG only looks at its nsISVGChildFrame |
|
3811 // children, and calls ConsiderChildOverflow on them. Does it matter |
|
3812 // that ConsiderChildOverflow won't be called on our children? |
|
3813 SVGTextFrameBase::ReflowSVG(); |
|
3814 } |
|
3815 |
|
3816 /** |
|
3817 * Converts nsSVGUtils::eBBox* flags into TextRenderedRun flags appropriate |
|
3818 * for the specified rendered run. |
|
3819 */ |
|
3820 static uint32_t |
|
3821 TextRenderedRunFlagsForBBoxContribution(const TextRenderedRun& aRun, |
|
3822 uint32_t aBBoxFlags) |
|
3823 { |
|
3824 uint32_t flags = 0; |
|
3825 if ((aBBoxFlags & nsSVGUtils::eBBoxIncludeFillGeometry) || |
|
3826 ((aBBoxFlags & nsSVGUtils::eBBoxIncludeFill) && |
|
3827 aRun.mFrame->StyleSVG()->mFill.mType != eStyleSVGPaintType_None)) { |
|
3828 flags |= TextRenderedRun::eIncludeFill; |
|
3829 } |
|
3830 if ((aBBoxFlags & nsSVGUtils::eBBoxIncludeStrokeGeometry) || |
|
3831 ((aBBoxFlags & nsSVGUtils::eBBoxIncludeStroke) && |
|
3832 nsSVGUtils::HasStroke(aRun.mFrame))) { |
|
3833 flags |= TextRenderedRun::eIncludeStroke; |
|
3834 } |
|
3835 return flags; |
|
3836 } |
|
3837 |
|
3838 SVGBBox |
|
3839 SVGTextFrame::GetBBoxContribution(const gfx::Matrix &aToBBoxUserspace, |
|
3840 uint32_t aFlags) |
|
3841 { |
|
3842 NS_ASSERTION(GetFirstPrincipalChild(), "must have a child frame"); |
|
3843 |
|
3844 UpdateGlyphPositioning(); |
|
3845 |
|
3846 SVGBBox bbox; |
|
3847 nsPresContext* presContext = PresContext(); |
|
3848 |
|
3849 TextRenderedRunIterator it(this); |
|
3850 for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { |
|
3851 uint32_t flags = TextRenderedRunFlagsForBBoxContribution(run, aFlags); |
|
3852 gfxMatrix m = ThebesMatrix(aToBBoxUserspace); |
|
3853 SVGBBox bboxForRun = |
|
3854 run.GetUserSpaceRect(presContext, flags, &m); |
|
3855 bbox.UnionEdges(bboxForRun); |
|
3856 } |
|
3857 |
|
3858 return bbox; |
|
3859 } |
|
3860 |
|
3861 //---------------------------------------------------------------------- |
|
3862 // nsSVGContainerFrame methods |
|
3863 |
|
3864 gfxMatrix |
|
3865 SVGTextFrame::GetCanvasTM(uint32_t aFor, nsIFrame* aTransformRoot) |
|
3866 { |
|
3867 if (!(GetStateBits() & NS_FRAME_IS_NONDISPLAY) && |
|
3868 !aTransformRoot) { |
|
3869 if ((aFor == FOR_PAINTING && NS_SVGDisplayListPaintingEnabled()) || |
|
3870 (aFor == FOR_HIT_TESTING && NS_SVGDisplayListHitTestingEnabled())) { |
|
3871 return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(this); |
|
3872 } |
|
3873 } |
|
3874 if (!mCanvasTM) { |
|
3875 NS_ASSERTION(mParent, "null parent"); |
|
3876 NS_ASSERTION(!(aFor == FOR_OUTERSVG_TM && |
|
3877 (GetStateBits() & NS_FRAME_IS_NONDISPLAY)), |
|
3878 "should not call GetCanvasTM(FOR_OUTERSVG_TM) when we are " |
|
3879 "non-display"); |
|
3880 |
|
3881 nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(mParent); |
|
3882 dom::SVGTextContentElement *content = static_cast<dom::SVGTextContentElement*>(mContent); |
|
3883 |
|
3884 gfxMatrix tm = content->PrependLocalTransformsTo( |
|
3885 this == aTransformRoot ? gfxMatrix() : |
|
3886 parent->GetCanvasTM(aFor, aTransformRoot)); |
|
3887 |
|
3888 mCanvasTM = new gfxMatrix(tm); |
|
3889 } |
|
3890 return *mCanvasTM; |
|
3891 } |
|
3892 |
|
3893 //---------------------------------------------------------------------- |
|
3894 // SVGTextFrame SVG DOM methods |
|
3895 |
|
3896 /** |
|
3897 * Returns whether the specified node has any non-empty nsTextNodes |
|
3898 * beneath it. |
|
3899 */ |
|
3900 static bool |
|
3901 HasTextContent(nsIContent* aContent) |
|
3902 { |
|
3903 NS_ASSERTION(aContent, "expected non-null aContent"); |
|
3904 |
|
3905 TextNodeIterator it(aContent); |
|
3906 for (nsTextNode* text = it.Current(); text; text = it.Next()) { |
|
3907 if (text->TextLength() != 0) { |
|
3908 return true; |
|
3909 } |
|
3910 } |
|
3911 return false; |
|
3912 } |
|
3913 |
|
3914 /** |
|
3915 * Returns the number of DOM characters beneath the specified node. |
|
3916 */ |
|
3917 static uint32_t |
|
3918 GetTextContentLength(nsIContent* aContent) |
|
3919 { |
|
3920 NS_ASSERTION(aContent, "expected non-null aContent"); |
|
3921 |
|
3922 uint32_t length = 0; |
|
3923 TextNodeIterator it(aContent); |
|
3924 for (nsTextNode* text = it.Current(); text; text = it.Next()) { |
|
3925 length += text->TextLength(); |
|
3926 } |
|
3927 return length; |
|
3928 } |
|
3929 |
|
3930 int32_t |
|
3931 SVGTextFrame::ConvertTextElementCharIndexToAddressableIndex( |
|
3932 int32_t aIndex, |
|
3933 nsIContent* aContent) |
|
3934 { |
|
3935 CharIterator it(this, CharIterator::eOriginal, aContent); |
|
3936 if (!it.AdvanceToSubtree()) { |
|
3937 return -1; |
|
3938 } |
|
3939 int32_t result = 0; |
|
3940 int32_t textElementCharIndex; |
|
3941 while (!it.AtEnd() && |
|
3942 it.IsWithinSubtree()) { |
|
3943 bool addressable = !it.IsOriginalCharUnaddressable(); |
|
3944 textElementCharIndex = it.TextElementCharIndex(); |
|
3945 it.Next(); |
|
3946 uint32_t delta = it.TextElementCharIndex() - textElementCharIndex; |
|
3947 aIndex -= delta; |
|
3948 if (addressable) { |
|
3949 if (aIndex < 0) { |
|
3950 return result; |
|
3951 } |
|
3952 result += delta; |
|
3953 } |
|
3954 } |
|
3955 return -1; |
|
3956 } |
|
3957 |
|
3958 /** |
|
3959 * Implements the SVG DOM GetNumberOfChars method for the specified |
|
3960 * text content element. |
|
3961 */ |
|
3962 uint32_t |
|
3963 SVGTextFrame::GetNumberOfChars(nsIContent* aContent) |
|
3964 { |
|
3965 UpdateGlyphPositioning(); |
|
3966 |
|
3967 uint32_t n = 0; |
|
3968 CharIterator it(this, CharIterator::eAddressable, aContent); |
|
3969 if (it.AdvanceToSubtree()) { |
|
3970 while (!it.AtEnd() && it.IsWithinSubtree()) { |
|
3971 n++; |
|
3972 it.Next(); |
|
3973 } |
|
3974 } |
|
3975 return n; |
|
3976 } |
|
3977 |
|
3978 /** |
|
3979 * Implements the SVG DOM GetComputedTextLength method for the specified |
|
3980 * text child element. |
|
3981 */ |
|
3982 float |
|
3983 SVGTextFrame::GetComputedTextLength(nsIContent* aContent) |
|
3984 { |
|
3985 UpdateGlyphPositioning(); |
|
3986 |
|
3987 float cssPxPerDevPx = PresContext()-> |
|
3988 AppUnitsToFloatCSSPixels(PresContext()->AppUnitsPerDevPixel()); |
|
3989 |
|
3990 nscoord length = 0; |
|
3991 TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames, |
|
3992 aContent); |
|
3993 for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { |
|
3994 length += run.GetAdvanceWidth(); |
|
3995 } |
|
3996 |
|
3997 return PresContext()->AppUnitsToGfxUnits(length) * |
|
3998 cssPxPerDevPx * mLengthAdjustScaleFactor / mFontSizeScaleFactor; |
|
3999 } |
|
4000 |
|
4001 /** |
|
4002 * Implements the SVG DOM SelectSubString method for the specified |
|
4003 * text content element. |
|
4004 */ |
|
4005 nsresult |
|
4006 SVGTextFrame::SelectSubString(nsIContent* aContent, |
|
4007 uint32_t charnum, uint32_t nchars) |
|
4008 { |
|
4009 UpdateGlyphPositioning(); |
|
4010 |
|
4011 // Convert charnum/nchars from addressable characters relative to |
|
4012 // aContent to global character indices. |
|
4013 CharIterator chit(this, CharIterator::eAddressable, aContent); |
|
4014 if (!chit.AdvanceToSubtree() || |
|
4015 !chit.Next(charnum) || |
|
4016 chit.IsAfterSubtree()) { |
|
4017 return NS_ERROR_DOM_INDEX_SIZE_ERR; |
|
4018 } |
|
4019 charnum = chit.TextElementCharIndex(); |
|
4020 nsIContent* content = chit.TextFrame()->GetContent(); |
|
4021 chit.NextWithinSubtree(nchars); |
|
4022 nchars = chit.TextElementCharIndex() - charnum; |
|
4023 |
|
4024 nsRefPtr<nsFrameSelection> frameSelection = GetFrameSelection(); |
|
4025 |
|
4026 frameSelection->HandleClick(content, charnum, charnum + nchars, |
|
4027 false, false, false); |
|
4028 return NS_OK; |
|
4029 } |
|
4030 |
|
4031 /** |
|
4032 * Implements the SVG DOM GetSubStringLength method for the specified |
|
4033 * text content element. |
|
4034 */ |
|
4035 nsresult |
|
4036 SVGTextFrame::GetSubStringLength(nsIContent* aContent, |
|
4037 uint32_t charnum, uint32_t nchars, |
|
4038 float* aResult) |
|
4039 { |
|
4040 UpdateGlyphPositioning(); |
|
4041 |
|
4042 // Convert charnum/nchars from addressable characters relative to |
|
4043 // aContent to global character indices. |
|
4044 CharIterator chit(this, CharIterator::eAddressable, aContent); |
|
4045 if (!chit.AdvanceToSubtree() || |
|
4046 !chit.Next(charnum) || |
|
4047 chit.IsAfterSubtree()) { |
|
4048 return NS_ERROR_DOM_INDEX_SIZE_ERR; |
|
4049 } |
|
4050 |
|
4051 if (nchars == 0) { |
|
4052 *aResult = 0.0f; |
|
4053 return NS_OK; |
|
4054 } |
|
4055 |
|
4056 charnum = chit.TextElementCharIndex(); |
|
4057 chit.NextWithinSubtree(nchars); |
|
4058 nchars = chit.TextElementCharIndex() - charnum; |
|
4059 |
|
4060 // Find each rendered run that intersects with the range defined |
|
4061 // by charnum/nchars. |
|
4062 nscoord textLength = 0; |
|
4063 TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames); |
|
4064 TextRenderedRun run = it.Current(); |
|
4065 while (run.mFrame) { |
|
4066 // If this rendered run is past the substring we are interested in, we |
|
4067 // are done. |
|
4068 uint32_t offset = run.mTextElementCharIndex; |
|
4069 if (offset >= charnum + nchars) { |
|
4070 break; |
|
4071 } |
|
4072 |
|
4073 // Intersect the substring we are interested in with the range covered by |
|
4074 // the rendered run. |
|
4075 uint32_t length = run.mTextFrameContentLength; |
|
4076 IntersectInterval(offset, length, charnum, nchars); |
|
4077 |
|
4078 if (length != 0) { |
|
4079 // Convert offset into an index into the frame. |
|
4080 offset += run.mTextFrameContentOffset - run.mTextElementCharIndex; |
|
4081 |
|
4082 gfxSkipCharsIterator it = |
|
4083 run.mFrame->EnsureTextRun(nsTextFrame::eInflated); |
|
4084 gfxTextRun* textRun = run.mFrame->GetTextRun(nsTextFrame::eInflated); |
|
4085 ConvertOriginalToSkipped(it, offset, length); |
|
4086 |
|
4087 // Accumulate the advance. |
|
4088 textLength += textRun->GetAdvanceWidth(offset, length, nullptr); |
|
4089 } |
|
4090 |
|
4091 run = it.Next(); |
|
4092 } |
|
4093 |
|
4094 nsPresContext* presContext = PresContext(); |
|
4095 float cssPxPerDevPx = presContext-> |
|
4096 AppUnitsToFloatCSSPixels(presContext->AppUnitsPerDevPixel()); |
|
4097 |
|
4098 *aResult = presContext->AppUnitsToGfxUnits(textLength) * |
|
4099 cssPxPerDevPx / mFontSizeScaleFactor; |
|
4100 return NS_OK; |
|
4101 } |
|
4102 |
|
4103 /** |
|
4104 * Implements the SVG DOM GetCharNumAtPosition method for the specified |
|
4105 * text content element. |
|
4106 */ |
|
4107 int32_t |
|
4108 SVGTextFrame::GetCharNumAtPosition(nsIContent* aContent, |
|
4109 mozilla::nsISVGPoint* aPoint) |
|
4110 { |
|
4111 UpdateGlyphPositioning(); |
|
4112 |
|
4113 nsPresContext* context = PresContext(); |
|
4114 |
|
4115 gfxPoint p(aPoint->X(), aPoint->Y()); |
|
4116 |
|
4117 int32_t result = -1; |
|
4118 |
|
4119 TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames, aContent); |
|
4120 for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { |
|
4121 // Hit test this rendered run. Later runs will override earlier ones. |
|
4122 int32_t index = run.GetCharNumAtPosition(context, p); |
|
4123 if (index != -1) { |
|
4124 result = index + run.mTextElementCharIndex; |
|
4125 } |
|
4126 } |
|
4127 |
|
4128 if (result == -1) { |
|
4129 return result; |
|
4130 } |
|
4131 |
|
4132 return ConvertTextElementCharIndexToAddressableIndex(result, aContent); |
|
4133 } |
|
4134 |
|
4135 /** |
|
4136 * Implements the SVG DOM GetStartPositionOfChar method for the specified |
|
4137 * text content element. |
|
4138 */ |
|
4139 nsresult |
|
4140 SVGTextFrame::GetStartPositionOfChar(nsIContent* aContent, |
|
4141 uint32_t aCharNum, |
|
4142 mozilla::nsISVGPoint** aResult) |
|
4143 { |
|
4144 UpdateGlyphPositioning(); |
|
4145 |
|
4146 CharIterator it(this, CharIterator::eAddressable, aContent); |
|
4147 if (!it.AdvanceToSubtree() || |
|
4148 !it.Next(aCharNum)) { |
|
4149 return NS_ERROR_DOM_INDEX_SIZE_ERR; |
|
4150 } |
|
4151 |
|
4152 // We need to return the start position of the whole glyph. |
|
4153 uint32_t startIndex = it.GlyphStartTextElementCharIndex(); |
|
4154 |
|
4155 NS_ADDREF(*aResult = |
|
4156 new DOMSVGPoint(ToPoint(mPositions[startIndex].mPosition))); |
|
4157 return NS_OK; |
|
4158 } |
|
4159 |
|
4160 /** |
|
4161 * Implements the SVG DOM GetEndPositionOfChar method for the specified |
|
4162 * text content element. |
|
4163 */ |
|
4164 nsresult |
|
4165 SVGTextFrame::GetEndPositionOfChar(nsIContent* aContent, |
|
4166 uint32_t aCharNum, |
|
4167 mozilla::nsISVGPoint** aResult) |
|
4168 { |
|
4169 UpdateGlyphPositioning(); |
|
4170 |
|
4171 CharIterator it(this, CharIterator::eAddressable, aContent); |
|
4172 if (!it.AdvanceToSubtree() || |
|
4173 !it.Next(aCharNum)) { |
|
4174 return NS_ERROR_DOM_INDEX_SIZE_ERR; |
|
4175 } |
|
4176 |
|
4177 // We need to return the end position of the whole glyph. |
|
4178 uint32_t startIndex = it.GlyphStartTextElementCharIndex(); |
|
4179 |
|
4180 // Get the advance of the glyph. |
|
4181 gfxFloat advance = it.GetGlyphAdvance(PresContext()); |
|
4182 if (it.TextRun()->IsRightToLeft()) { |
|
4183 advance = -advance; |
|
4184 } |
|
4185 |
|
4186 // The end position is the start position plus the advance in the direction |
|
4187 // of the glyph's rotation. |
|
4188 Matrix m = |
|
4189 Matrix::Rotation(mPositions[startIndex].mAngle) * |
|
4190 Matrix::Translation(ToPoint(mPositions[startIndex].mPosition)); |
|
4191 Point p = m * Point(advance / mFontSizeScaleFactor, 0); |
|
4192 |
|
4193 NS_ADDREF(*aResult = new DOMSVGPoint(p)); |
|
4194 return NS_OK; |
|
4195 } |
|
4196 |
|
4197 /** |
|
4198 * Implements the SVG DOM GetExtentOfChar method for the specified |
|
4199 * text content element. |
|
4200 */ |
|
4201 nsresult |
|
4202 SVGTextFrame::GetExtentOfChar(nsIContent* aContent, |
|
4203 uint32_t aCharNum, |
|
4204 dom::SVGIRect** aResult) |
|
4205 { |
|
4206 UpdateGlyphPositioning(); |
|
4207 |
|
4208 CharIterator it(this, CharIterator::eAddressable, aContent); |
|
4209 if (!it.AdvanceToSubtree() || |
|
4210 !it.Next(aCharNum)) { |
|
4211 return NS_ERROR_DOM_INDEX_SIZE_ERR; |
|
4212 } |
|
4213 |
|
4214 nsPresContext* presContext = PresContext(); |
|
4215 |
|
4216 float cssPxPerDevPx = presContext-> |
|
4217 AppUnitsToFloatCSSPixels(presContext->AppUnitsPerDevPixel()); |
|
4218 |
|
4219 // We need to return the extent of the whole glyph. |
|
4220 uint32_t startIndex = it.GlyphStartTextElementCharIndex(); |
|
4221 |
|
4222 // The ascent and descent gives the height of the glyph. |
|
4223 gfxFloat ascent, descent; |
|
4224 GetAscentAndDescentInAppUnits(it.TextFrame(), ascent, descent); |
|
4225 |
|
4226 // Get the advance of the glyph. |
|
4227 gfxFloat advance = it.GetGlyphAdvance(presContext); |
|
4228 gfxFloat x = it.TextRun()->IsRightToLeft() ? -advance : 0.0; |
|
4229 |
|
4230 // The horizontal extent is the origin of the glyph plus the advance |
|
4231 // in the direction of the glyph's rotation. |
|
4232 gfxMatrix m; |
|
4233 m.Translate(mPositions[startIndex].mPosition); |
|
4234 m.Rotate(mPositions[startIndex].mAngle); |
|
4235 m.Scale(1 / mFontSizeScaleFactor, 1 / mFontSizeScaleFactor); |
|
4236 |
|
4237 gfxRect glyphRect |
|
4238 (x, -presContext->AppUnitsToGfxUnits(ascent) * cssPxPerDevPx, |
|
4239 advance, presContext->AppUnitsToGfxUnits(ascent + descent) * cssPxPerDevPx); |
|
4240 |
|
4241 // Transform the glyph's rect into user space. |
|
4242 gfxRect r = m.TransformBounds(glyphRect); |
|
4243 |
|
4244 NS_ADDREF(*aResult = new dom::SVGRect(aContent, r.x, r.y, r.width, r.height)); |
|
4245 return NS_OK; |
|
4246 } |
|
4247 |
|
4248 /** |
|
4249 * Implements the SVG DOM GetRotationOfChar method for the specified |
|
4250 * text content element. |
|
4251 */ |
|
4252 nsresult |
|
4253 SVGTextFrame::GetRotationOfChar(nsIContent* aContent, |
|
4254 uint32_t aCharNum, |
|
4255 float* aResult) |
|
4256 { |
|
4257 UpdateGlyphPositioning(); |
|
4258 |
|
4259 CharIterator it(this, CharIterator::eAddressable, aContent); |
|
4260 if (!it.AdvanceToSubtree() || |
|
4261 !it.Next(aCharNum)) { |
|
4262 return NS_ERROR_DOM_INDEX_SIZE_ERR; |
|
4263 } |
|
4264 |
|
4265 *aResult = mPositions[it.TextElementCharIndex()].mAngle * 180.0 / M_PI; |
|
4266 return NS_OK; |
|
4267 } |
|
4268 |
|
4269 //---------------------------------------------------------------------- |
|
4270 // SVGTextFrame text layout methods |
|
4271 |
|
4272 /** |
|
4273 * Given the character position array before values have been filled in |
|
4274 * to any unspecified positions, and an array of dx/dy values, returns whether |
|
4275 * a character at a given index should start a new rendered run. |
|
4276 * |
|
4277 * @param aPositions The array of character positions before unspecified |
|
4278 * positions have been filled in and dx/dy values have been added to them. |
|
4279 * @param aDeltas The array of dx/dy values. |
|
4280 * @param aIndex The character index in question. |
|
4281 */ |
|
4282 static bool |
|
4283 ShouldStartRunAtIndex(const nsTArray<CharPosition>& aPositions, |
|
4284 const nsTArray<gfxPoint>& aDeltas, |
|
4285 uint32_t aIndex) |
|
4286 { |
|
4287 if (aIndex == 0) { |
|
4288 return true; |
|
4289 } |
|
4290 |
|
4291 if (aIndex < aPositions.Length()) { |
|
4292 // If an explicit x or y value was given, start a new run. |
|
4293 if (aPositions[aIndex].IsXSpecified() || |
|
4294 aPositions[aIndex].IsYSpecified()) { |
|
4295 return true; |
|
4296 } |
|
4297 |
|
4298 // If a non-zero rotation was given, or the previous character had a non- |
|
4299 // zero rotation, start a new run. |
|
4300 if ((aPositions[aIndex].IsAngleSpecified() && |
|
4301 aPositions[aIndex].mAngle != 0.0f) || |
|
4302 (aPositions[aIndex - 1].IsAngleSpecified() && |
|
4303 (aPositions[aIndex - 1].mAngle != 0.0f))) { |
|
4304 return true; |
|
4305 } |
|
4306 } |
|
4307 |
|
4308 if (aIndex < aDeltas.Length()) { |
|
4309 // If a non-zero dx or dy value was given, start a new run. |
|
4310 if (aDeltas[aIndex].x != 0.0 || |
|
4311 aDeltas[aIndex].y != 0.0) { |
|
4312 return true; |
|
4313 } |
|
4314 } |
|
4315 |
|
4316 return false; |
|
4317 } |
|
4318 |
|
4319 uint32_t |
|
4320 SVGTextFrame::ResolvePositions(nsIContent* aContent, |
|
4321 uint32_t aIndex, |
|
4322 bool aInTextPath, |
|
4323 bool& aForceStartOfChunk, |
|
4324 nsTArray<gfxPoint>& aDeltas) |
|
4325 { |
|
4326 if (aContent->IsNodeOfType(nsINode::eTEXT)) { |
|
4327 // We found a text node. |
|
4328 uint32_t length = static_cast<nsTextNode*>(aContent)->TextLength(); |
|
4329 if (length) { |
|
4330 if (aForceStartOfChunk) { |
|
4331 // Note this character as starting a new anchored chunk. |
|
4332 mPositions[aIndex].mStartOfChunk = true; |
|
4333 aForceStartOfChunk = false; |
|
4334 } |
|
4335 uint32_t end = aIndex + length; |
|
4336 while (aIndex < end) { |
|
4337 // Record whether each of these characters should start a new rendered |
|
4338 // run. That is always the case for characters on a text path. |
|
4339 // |
|
4340 // Run boundaries due to rotate="" values are handled in |
|
4341 // DoGlyphPositioning. |
|
4342 if (aInTextPath || ShouldStartRunAtIndex(mPositions, aDeltas, aIndex)) { |
|
4343 mPositions[aIndex].mRunBoundary = true; |
|
4344 } |
|
4345 aIndex++; |
|
4346 } |
|
4347 } |
|
4348 return aIndex; |
|
4349 } |
|
4350 |
|
4351 // Skip past elements that aren't text content elements. |
|
4352 if (!IsTextContentElement(aContent)) { |
|
4353 return aIndex; |
|
4354 } |
|
4355 |
|
4356 if (aContent->Tag() == nsGkAtoms::textPath) { |
|
4357 // <textPath> elements are as if they are specified with x="0" y="0", but |
|
4358 // only if they actually have some text content. |
|
4359 if (HasTextContent(aContent)) { |
|
4360 mPositions[aIndex].mPosition = gfxPoint(); |
|
4361 mPositions[aIndex].mStartOfChunk = true; |
|
4362 } |
|
4363 } else if (aContent->Tag() != nsGkAtoms::a) { |
|
4364 // We have a text content element that can have x/y/dx/dy/rotate attributes. |
|
4365 nsSVGElement* element = static_cast<nsSVGElement*>(aContent); |
|
4366 |
|
4367 // Get x, y, dx, dy. |
|
4368 SVGUserUnitList x, y, dx, dy; |
|
4369 element->GetAnimatedLengthListValues(&x, &y, &dx, &dy); |
|
4370 |
|
4371 // Get rotate. |
|
4372 const SVGNumberList* rotate = nullptr; |
|
4373 SVGAnimatedNumberList* animatedRotate = |
|
4374 element->GetAnimatedNumberList(nsGkAtoms::rotate); |
|
4375 if (animatedRotate) { |
|
4376 rotate = &animatedRotate->GetAnimValue(); |
|
4377 } |
|
4378 |
|
4379 uint32_t count = GetTextContentLength(aContent); |
|
4380 bool percentages = false; |
|
4381 |
|
4382 // New text anchoring chunks start at each character assigned a position |
|
4383 // with x="" or y="", or if we forced one with aForceStartOfChunk due to |
|
4384 // being just after a <textPath>. |
|
4385 uint32_t newChunkCount = std::max(x.Length(), y.Length()); |
|
4386 if (!newChunkCount && aForceStartOfChunk) { |
|
4387 newChunkCount = 1; |
|
4388 } |
|
4389 for (uint32_t i = 0, j = 0; i < newChunkCount && j < count; j++) { |
|
4390 if (!mPositions[aIndex + j].mUnaddressable) { |
|
4391 mPositions[aIndex + j].mStartOfChunk = true; |
|
4392 i++; |
|
4393 } |
|
4394 } |
|
4395 |
|
4396 // Copy dx="" and dy="" values into aDeltas. |
|
4397 if (!dx.IsEmpty() || !dy.IsEmpty()) { |
|
4398 // Any unspecified deltas when we grow the array just get left as 0s. |
|
4399 aDeltas.EnsureLengthAtLeast(aIndex + count); |
|
4400 for (uint32_t i = 0, j = 0; i < dx.Length() && j < count; j++) { |
|
4401 if (!mPositions[aIndex + j].mUnaddressable) { |
|
4402 aDeltas[aIndex + j].x = dx[i]; |
|
4403 percentages = percentages || dx.HasPercentageValueAt(i); |
|
4404 i++; |
|
4405 } |
|
4406 } |
|
4407 for (uint32_t i = 0, j = 0; i < dy.Length() && j < count; j++) { |
|
4408 if (!mPositions[aIndex + j].mUnaddressable) { |
|
4409 aDeltas[aIndex + j].y = dy[i]; |
|
4410 percentages = percentages || dy.HasPercentageValueAt(i); |
|
4411 i++; |
|
4412 } |
|
4413 } |
|
4414 } |
|
4415 |
|
4416 // Copy x="" and y="" values. |
|
4417 for (uint32_t i = 0, j = 0; i < x.Length() && j < count; j++) { |
|
4418 if (!mPositions[aIndex + j].mUnaddressable) { |
|
4419 mPositions[aIndex + j].mPosition.x = x[i]; |
|
4420 percentages = percentages || x.HasPercentageValueAt(i); |
|
4421 i++; |
|
4422 } |
|
4423 } |
|
4424 for (uint32_t i = 0, j = 0; i < y.Length() && j < count; j++) { |
|
4425 if (!mPositions[aIndex + j].mUnaddressable) { |
|
4426 mPositions[aIndex + j].mPosition.y = y[i]; |
|
4427 percentages = percentages || y.HasPercentageValueAt(i); |
|
4428 i++; |
|
4429 } |
|
4430 } |
|
4431 |
|
4432 // Copy rotate="" values. |
|
4433 if (rotate && !rotate->IsEmpty()) { |
|
4434 uint32_t i = 0, j = 0; |
|
4435 while (i < rotate->Length() && j < count) { |
|
4436 if (!mPositions[aIndex + j].mUnaddressable) { |
|
4437 mPositions[aIndex + j].mAngle = M_PI * (*rotate)[i] / 180.0; |
|
4438 i++; |
|
4439 } |
|
4440 j++; |
|
4441 } |
|
4442 // Propagate final rotate="" value to the end of this element. |
|
4443 while (j < count) { |
|
4444 mPositions[aIndex + j].mAngle = mPositions[aIndex + j - 1].mAngle; |
|
4445 j++; |
|
4446 } |
|
4447 } |
|
4448 |
|
4449 if (percentages) { |
|
4450 AddStateBits(NS_STATE_SVG_POSITIONING_MAY_USE_PERCENTAGES); |
|
4451 } |
|
4452 } |
|
4453 |
|
4454 // Recurse to children. |
|
4455 bool inTextPath = aInTextPath || aContent->Tag() == nsGkAtoms::textPath; |
|
4456 for (nsIContent* child = aContent->GetFirstChild(); |
|
4457 child; |
|
4458 child = child->GetNextSibling()) { |
|
4459 aIndex = ResolvePositions(child, aIndex, inTextPath, aForceStartOfChunk, |
|
4460 aDeltas); |
|
4461 } |
|
4462 |
|
4463 if (aContent->Tag() == nsGkAtoms::textPath) { |
|
4464 // Force a new anchored chunk just after a <textPath>. |
|
4465 aForceStartOfChunk = true; |
|
4466 } |
|
4467 |
|
4468 return aIndex; |
|
4469 } |
|
4470 |
|
4471 bool |
|
4472 SVGTextFrame::ResolvePositions(nsTArray<gfxPoint>& aDeltas, |
|
4473 bool aRunPerGlyph) |
|
4474 { |
|
4475 NS_ASSERTION(mPositions.IsEmpty(), "expected mPositions to be empty"); |
|
4476 RemoveStateBits(NS_STATE_SVG_POSITIONING_MAY_USE_PERCENTAGES); |
|
4477 |
|
4478 CharIterator it(this, CharIterator::eOriginal); |
|
4479 if (it.AtEnd()) { |
|
4480 return false; |
|
4481 } |
|
4482 |
|
4483 // We assume the first character position is (0,0) unless we later see |
|
4484 // otherwise, and note it as unaddressable if it is. |
|
4485 bool firstCharUnaddressable = it.IsOriginalCharUnaddressable(); |
|
4486 mPositions.AppendElement(CharPosition::Unspecified(firstCharUnaddressable)); |
|
4487 |
|
4488 // Fill in unspecified positions for all remaining characters, noting |
|
4489 // them as unaddressable if they are. |
|
4490 uint32_t index = 0; |
|
4491 while (it.Next()) { |
|
4492 while (++index < it.TextElementCharIndex()) { |
|
4493 mPositions.AppendElement(CharPosition::Unspecified(false)); |
|
4494 } |
|
4495 mPositions.AppendElement(CharPosition::Unspecified( |
|
4496 it.IsOriginalCharUnaddressable())); |
|
4497 } |
|
4498 while (++index < it.TextElementCharIndex()) { |
|
4499 mPositions.AppendElement(CharPosition::Unspecified(false)); |
|
4500 } |
|
4501 |
|
4502 // Recurse over the content and fill in character positions as we go. |
|
4503 bool forceStartOfChunk = false; |
|
4504 return ResolvePositions(mContent, 0, aRunPerGlyph, |
|
4505 forceStartOfChunk, aDeltas) != 0; |
|
4506 } |
|
4507 |
|
4508 void |
|
4509 SVGTextFrame::DetermineCharPositions(nsTArray<nsPoint>& aPositions) |
|
4510 { |
|
4511 NS_ASSERTION(aPositions.IsEmpty(), "expected aPositions to be empty"); |
|
4512 |
|
4513 nsPoint position, lastPosition; |
|
4514 |
|
4515 TextFrameIterator frit(this); |
|
4516 for (nsTextFrame* frame = frit.Current(); frame; frame = frit.Next()) { |
|
4517 gfxSkipCharsIterator it = frame->EnsureTextRun(nsTextFrame::eInflated); |
|
4518 gfxTextRun* textRun = frame->GetTextRun(nsTextFrame::eInflated); |
|
4519 |
|
4520 // Reset the position to the new frame's position. |
|
4521 position = frit.Position(); |
|
4522 if (textRun->IsRightToLeft()) { |
|
4523 position.x += frame->GetRect().width; |
|
4524 } |
|
4525 position.y += GetBaselinePosition(frame, textRun, frit.DominantBaseline()); |
|
4526 |
|
4527 // Any characters not in a frame, e.g. when display:none. |
|
4528 for (uint32_t i = 0; i < frit.UndisplayedCharacters(); i++) { |
|
4529 aPositions.AppendElement(position); |
|
4530 } |
|
4531 |
|
4532 // Any white space characters trimmed at the start of the line of text. |
|
4533 nsTextFrame::TrimmedOffsets trimmedOffsets = |
|
4534 frame->GetTrimmedOffsets(frame->GetContent()->GetText(), true); |
|
4535 while (it.GetOriginalOffset() < trimmedOffsets.mStart) { |
|
4536 aPositions.AppendElement(position); |
|
4537 it.AdvanceOriginal(1); |
|
4538 } |
|
4539 |
|
4540 // If a ligature was started in the previous frame, we should record |
|
4541 // the ligature's start position, not any partial position. |
|
4542 while (it.GetOriginalOffset() < frame->GetContentEnd() && |
|
4543 !it.IsOriginalCharSkipped() && |
|
4544 (!textRun->IsLigatureGroupStart(it.GetSkippedOffset()) || |
|
4545 !textRun->IsClusterStart(it.GetSkippedOffset()))) { |
|
4546 nscoord advance = textRun->GetAdvanceWidth(it.GetSkippedOffset(), 1, |
|
4547 nullptr); |
|
4548 position.x += textRun->IsRightToLeft() ? -advance : advance; |
|
4549 aPositions.AppendElement(lastPosition); |
|
4550 it.AdvanceOriginal(1); |
|
4551 } |
|
4552 |
|
4553 // The meat of the text frame. |
|
4554 while (it.GetOriginalOffset() < frame->GetContentEnd()) { |
|
4555 aPositions.AppendElement(position); |
|
4556 if (!it.IsOriginalCharSkipped() && |
|
4557 textRun->IsLigatureGroupStart(it.GetSkippedOffset()) && |
|
4558 textRun->IsClusterStart(it.GetSkippedOffset())) { |
|
4559 // A real visible character. |
|
4560 uint32_t length = ClusterLength(textRun, it); |
|
4561 nscoord advance = textRun->GetAdvanceWidth(it.GetSkippedOffset(), |
|
4562 length, nullptr); |
|
4563 position.x += textRun->IsRightToLeft() ? -advance : advance; |
|
4564 lastPosition = position; |
|
4565 } |
|
4566 it.AdvanceOriginal(1); |
|
4567 } |
|
4568 } |
|
4569 |
|
4570 // Finally any characters at the end that are not in a frame. |
|
4571 for (uint32_t i = 0; i < frit.UndisplayedCharacters(); i++) { |
|
4572 aPositions.AppendElement(position); |
|
4573 } |
|
4574 } |
|
4575 |
|
4576 /** |
|
4577 * Physical text-anchor values. |
|
4578 */ |
|
4579 enum TextAnchorSide { |
|
4580 eAnchorLeft, |
|
4581 eAnchorMiddle, |
|
4582 eAnchorRight |
|
4583 }; |
|
4584 |
|
4585 /** |
|
4586 * Converts a logical text-anchor value to its physical value, based on whether |
|
4587 * it is for an RTL frame. |
|
4588 */ |
|
4589 static TextAnchorSide |
|
4590 ConvertLogicalTextAnchorToPhysical(uint8_t aTextAnchor, bool aIsRightToLeft) |
|
4591 { |
|
4592 NS_ASSERTION(aTextAnchor <= 3, "unexpected value for aTextAnchor"); |
|
4593 if (!aIsRightToLeft) |
|
4594 return TextAnchorSide(aTextAnchor); |
|
4595 return TextAnchorSide(2 - aTextAnchor); |
|
4596 } |
|
4597 |
|
4598 /** |
|
4599 * Shifts the recorded character positions for an anchored chunk. |
|
4600 * |
|
4601 * @param aCharPositions The recorded character positions. |
|
4602 * @param aChunkStart The character index the starts the anchored chunk. This |
|
4603 * character's initial position is the anchor point. |
|
4604 * @param aChunkEnd The character index just after the end of the anchored |
|
4605 * chunk. |
|
4606 * @param aLeftEdge The left-most edge of any of the glyphs within the |
|
4607 * anchored chunk. |
|
4608 * @param aRightEdge The right-most edge of any of the glyphs within the |
|
4609 * anchored chunk. |
|
4610 * @param aAnchorSide The direction to anchor. |
|
4611 */ |
|
4612 static void |
|
4613 ShiftAnchoredChunk(nsTArray<mozilla::CharPosition>& aCharPositions, |
|
4614 uint32_t aChunkStart, |
|
4615 uint32_t aChunkEnd, |
|
4616 gfxFloat aLeftEdge, |
|
4617 gfxFloat aRightEdge, |
|
4618 TextAnchorSide aAnchorSide) |
|
4619 { |
|
4620 NS_ASSERTION(aLeftEdge <= aRightEdge, "unexpected anchored chunk edges"); |
|
4621 NS_ASSERTION(aChunkStart < aChunkEnd, "unexpected values for aChunkStart and " |
|
4622 "aChunkEnd"); |
|
4623 |
|
4624 gfxFloat shift = aCharPositions[aChunkStart].mPosition.x; |
|
4625 switch (aAnchorSide) { |
|
4626 case eAnchorLeft: |
|
4627 shift -= aLeftEdge; |
|
4628 break; |
|
4629 case eAnchorMiddle: |
|
4630 shift -= (aLeftEdge + aRightEdge) / 2; |
|
4631 break; |
|
4632 case eAnchorRight: |
|
4633 shift -= aRightEdge; |
|
4634 break; |
|
4635 default: |
|
4636 NS_NOTREACHED("unexpected value for aAnchorSide"); |
|
4637 } |
|
4638 |
|
4639 if (shift != 0.0) { |
|
4640 for (uint32_t i = aChunkStart; i < aChunkEnd; i++) { |
|
4641 aCharPositions[i].mPosition.x += shift; |
|
4642 } |
|
4643 } |
|
4644 } |
|
4645 |
|
4646 void |
|
4647 SVGTextFrame::AdjustChunksForLineBreaks() |
|
4648 { |
|
4649 nsBlockFrame* block = nsLayoutUtils::GetAsBlock(GetFirstPrincipalChild()); |
|
4650 NS_ASSERTION(block, "expected block frame"); |
|
4651 |
|
4652 nsBlockFrame::line_iterator line = block->begin_lines(); |
|
4653 |
|
4654 CharIterator it(this, CharIterator::eOriginal); |
|
4655 while (!it.AtEnd() && line != block->end_lines()) { |
|
4656 if (it.TextFrame() == line->mFirstChild) { |
|
4657 mPositions[it.TextElementCharIndex()].mStartOfChunk = true; |
|
4658 line++; |
|
4659 } |
|
4660 it.AdvancePastCurrentFrame(); |
|
4661 } |
|
4662 } |
|
4663 |
|
4664 void |
|
4665 SVGTextFrame::AdjustPositionsForClusters() |
|
4666 { |
|
4667 nsPresContext* presContext = PresContext(); |
|
4668 |
|
4669 CharIterator it(this, CharIterator::eClusterOrLigatureGroupMiddle); |
|
4670 while (!it.AtEnd()) { |
|
4671 // Find the start of the cluster/ligature group. |
|
4672 uint32_t charIndex = it.TextElementCharIndex(); |
|
4673 uint32_t startIndex = it.GlyphStartTextElementCharIndex(); |
|
4674 |
|
4675 mPositions[charIndex].mClusterOrLigatureGroupMiddle = true; |
|
4676 |
|
4677 // Don't allow different rotations on ligature parts. |
|
4678 bool rotationAdjusted = false; |
|
4679 double angle = mPositions[startIndex].mAngle; |
|
4680 if (mPositions[charIndex].mAngle != angle) { |
|
4681 mPositions[charIndex].mAngle = angle; |
|
4682 rotationAdjusted = true; |
|
4683 } |
|
4684 |
|
4685 // Find out the partial glyph advance for this character and update |
|
4686 // the character position. |
|
4687 uint32_t partLength = |
|
4688 charIndex - startIndex - it.GlyphUndisplayedCharacters(); |
|
4689 gfxFloat advance = |
|
4690 it.GetGlyphPartialAdvance(partLength, presContext) / mFontSizeScaleFactor; |
|
4691 gfxPoint direction = gfxPoint(cos(angle), sin(angle)) * |
|
4692 (it.TextRun()->IsRightToLeft() ? -1.0 : 1.0); |
|
4693 mPositions[charIndex].mPosition = mPositions[startIndex].mPosition + |
|
4694 direction * advance; |
|
4695 |
|
4696 // Ensure any runs that would end in the middle of a ligature now end just |
|
4697 // after the ligature. |
|
4698 if (mPositions[charIndex].mRunBoundary) { |
|
4699 mPositions[charIndex].mRunBoundary = false; |
|
4700 if (charIndex + 1 < mPositions.Length()) { |
|
4701 mPositions[charIndex + 1].mRunBoundary = true; |
|
4702 } |
|
4703 } else if (rotationAdjusted) { |
|
4704 if (charIndex + 1 < mPositions.Length()) { |
|
4705 mPositions[charIndex + 1].mRunBoundary = true; |
|
4706 } |
|
4707 } |
|
4708 |
|
4709 // Ensure any anchored chunks that would begin in the middle of a ligature |
|
4710 // now begin just after the ligature. |
|
4711 if (mPositions[charIndex].mStartOfChunk) { |
|
4712 mPositions[charIndex].mStartOfChunk = false; |
|
4713 if (charIndex + 1 < mPositions.Length()) { |
|
4714 mPositions[charIndex + 1].mStartOfChunk = true; |
|
4715 } |
|
4716 } |
|
4717 |
|
4718 it.Next(); |
|
4719 } |
|
4720 } |
|
4721 |
|
4722 nsIFrame* |
|
4723 SVGTextFrame::GetTextPathPathFrame(nsIFrame* aTextPathFrame) |
|
4724 { |
|
4725 nsSVGTextPathProperty *property = static_cast<nsSVGTextPathProperty*> |
|
4726 (aTextPathFrame->Properties().Get(nsSVGEffects::HrefProperty())); |
|
4727 |
|
4728 if (!property) { |
|
4729 nsIContent* content = aTextPathFrame->GetContent(); |
|
4730 dom::SVGTextPathElement* tp = static_cast<dom::SVGTextPathElement*>(content); |
|
4731 nsAutoString href; |
|
4732 tp->mStringAttributes[dom::SVGTextPathElement::HREF].GetAnimValue(href, tp); |
|
4733 if (href.IsEmpty()) { |
|
4734 return nullptr; // no URL |
|
4735 } |
|
4736 |
|
4737 nsCOMPtr<nsIURI> targetURI; |
|
4738 nsCOMPtr<nsIURI> base = content->GetBaseURI(); |
|
4739 nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI), href, |
|
4740 content->GetCurrentDoc(), base); |
|
4741 |
|
4742 property = nsSVGEffects::GetTextPathProperty(targetURI, aTextPathFrame, |
|
4743 nsSVGEffects::HrefProperty()); |
|
4744 if (!property) |
|
4745 return nullptr; |
|
4746 } |
|
4747 |
|
4748 return property->GetReferencedFrame(nsGkAtoms::svgPathGeometryFrame, nullptr); |
|
4749 } |
|
4750 |
|
4751 TemporaryRef<Path> |
|
4752 SVGTextFrame::GetTextPath(nsIFrame* aTextPathFrame) |
|
4753 { |
|
4754 nsIFrame *pathFrame = GetTextPathPathFrame(aTextPathFrame); |
|
4755 |
|
4756 if (!pathFrame) { |
|
4757 return nullptr; |
|
4758 } |
|
4759 |
|
4760 nsSVGPathGeometryElement *element = |
|
4761 static_cast<nsSVGPathGeometryElement*>(pathFrame->GetContent()); |
|
4762 |
|
4763 RefPtr<Path> path = element->GetPathForLengthOrPositionMeasuring(); |
|
4764 if (!path) { |
|
4765 return nullptr; |
|
4766 } |
|
4767 |
|
4768 gfxMatrix matrix = element->PrependLocalTransformsTo(gfxMatrix()); |
|
4769 if (!matrix.IsIdentity()) { |
|
4770 RefPtr<PathBuilder> builder = |
|
4771 path->TransformedCopyToBuilder(ToMatrix(matrix)); |
|
4772 path = builder->Finish(); |
|
4773 } |
|
4774 |
|
4775 return path.forget(); |
|
4776 } |
|
4777 |
|
4778 gfxFloat |
|
4779 SVGTextFrame::GetOffsetScale(nsIFrame* aTextPathFrame) |
|
4780 { |
|
4781 nsIFrame *pathFrame = GetTextPathPathFrame(aTextPathFrame); |
|
4782 if (!pathFrame) |
|
4783 return 1.0; |
|
4784 |
|
4785 return static_cast<dom::SVGPathElement*>(pathFrame->GetContent())-> |
|
4786 GetPathLengthScale(dom::SVGPathElement::eForTextPath); |
|
4787 } |
|
4788 |
|
4789 gfxFloat |
|
4790 SVGTextFrame::GetStartOffset(nsIFrame* aTextPathFrame) |
|
4791 { |
|
4792 dom::SVGTextPathElement *tp = |
|
4793 static_cast<dom::SVGTextPathElement*>(aTextPathFrame->GetContent()); |
|
4794 nsSVGLength2 *length = |
|
4795 &tp->mLengthAttributes[dom::SVGTextPathElement::STARTOFFSET]; |
|
4796 |
|
4797 if (length->IsPercentage()) { |
|
4798 RefPtr<Path> data = GetTextPath(aTextPathFrame); |
|
4799 return data ? |
|
4800 length->GetAnimValInSpecifiedUnits() * data->ComputeLength() / 100.0 : |
|
4801 0.0; |
|
4802 } |
|
4803 return length->GetAnimValue(tp) * GetOffsetScale(aTextPathFrame); |
|
4804 } |
|
4805 |
|
4806 void |
|
4807 SVGTextFrame::DoTextPathLayout() |
|
4808 { |
|
4809 nsPresContext* context = PresContext(); |
|
4810 |
|
4811 CharIterator it(this, CharIterator::eClusterAndLigatureGroupStart); |
|
4812 while (!it.AtEnd()) { |
|
4813 nsIFrame* textPathFrame = it.TextPathFrame(); |
|
4814 if (!textPathFrame) { |
|
4815 // Skip past this frame if we're not in a text path. |
|
4816 it.AdvancePastCurrentFrame(); |
|
4817 continue; |
|
4818 } |
|
4819 |
|
4820 // Get the path itself. |
|
4821 RefPtr<Path> path = GetTextPath(textPathFrame); |
|
4822 if (!path) { |
|
4823 it.AdvancePastCurrentTextPathFrame(); |
|
4824 continue; |
|
4825 } |
|
4826 |
|
4827 nsIContent* textPath = textPathFrame->GetContent(); |
|
4828 |
|
4829 gfxFloat offset = GetStartOffset(textPathFrame); |
|
4830 Float pathLength = path->ComputeLength(); |
|
4831 |
|
4832 // Loop for each text frame in the text path. |
|
4833 do { |
|
4834 uint32_t i = it.TextElementCharIndex(); |
|
4835 gfxFloat halfAdvance = |
|
4836 it.GetGlyphAdvance(context) / mFontSizeScaleFactor / 2.0; |
|
4837 gfxFloat sign = it.TextRun()->IsRightToLeft() ? -1.0 : 1.0; |
|
4838 gfxFloat midx = mPositions[i].mPosition.x + sign * halfAdvance + offset; |
|
4839 |
|
4840 // Hide the character if it falls off the end of the path. |
|
4841 mPositions[i].mHidden = midx < 0 || midx > pathLength; |
|
4842 |
|
4843 // Position the character on the path at the right angle. |
|
4844 Point tangent; // Unit vector tangent to the point we find. |
|
4845 Point pt = path->ComputePointAtLength(Float(midx), &tangent); |
|
4846 Float rotation = atan2f(tangent.y, tangent.x); |
|
4847 Point normal(-tangent.y, tangent.x); // Unit vector normal to the point. |
|
4848 Point offsetFromPath = normal * mPositions[i].mPosition.y; |
|
4849 pt += offsetFromPath; |
|
4850 Point direction = tangent * sign; |
|
4851 mPositions[i].mPosition = ThebesPoint(pt) - ThebesPoint(direction) * halfAdvance; |
|
4852 mPositions[i].mAngle += rotation; |
|
4853 |
|
4854 // Position any characters for a partial ligature. |
|
4855 for (uint32_t j = i + 1; |
|
4856 j < mPositions.Length() && mPositions[j].mClusterOrLigatureGroupMiddle; |
|
4857 j++) { |
|
4858 gfxPoint partialAdvance = |
|
4859 ThebesPoint(direction) * it.GetGlyphPartialAdvance(j - i, context) / |
|
4860 mFontSizeScaleFactor; |
|
4861 mPositions[j].mPosition = mPositions[i].mPosition + partialAdvance; |
|
4862 mPositions[j].mAngle = mPositions[i].mAngle; |
|
4863 mPositions[j].mHidden = mPositions[i].mHidden; |
|
4864 } |
|
4865 it.Next(); |
|
4866 } while (it.TextPathFrame() && |
|
4867 it.TextPathFrame()->GetContent() == textPath); |
|
4868 } |
|
4869 } |
|
4870 |
|
4871 void |
|
4872 SVGTextFrame::DoAnchoring() |
|
4873 { |
|
4874 nsPresContext* presContext = PresContext(); |
|
4875 |
|
4876 CharIterator it(this, CharIterator::eOriginal); |
|
4877 |
|
4878 // Don't need to worry about skipped or trimmed characters. |
|
4879 while (!it.AtEnd() && |
|
4880 (it.IsOriginalCharSkipped() || it.IsOriginalCharTrimmed())) { |
|
4881 it.Next(); |
|
4882 } |
|
4883 |
|
4884 uint32_t start = it.TextElementCharIndex(); |
|
4885 while (start < mPositions.Length()) { |
|
4886 it.AdvanceToCharacter(start); |
|
4887 nsTextFrame* chunkFrame = it.TextFrame(); |
|
4888 |
|
4889 // Measure characters in this chunk to find the left-most and right-most |
|
4890 // edges of all glyphs within the chunk. |
|
4891 uint32_t index = it.TextElementCharIndex(); |
|
4892 uint32_t end = start; |
|
4893 gfxFloat left = std::numeric_limits<gfxFloat>::infinity(); |
|
4894 gfxFloat right = -std::numeric_limits<gfxFloat>::infinity(); |
|
4895 do { |
|
4896 if (!it.IsOriginalCharSkipped() && !it.IsOriginalCharTrimmed()) { |
|
4897 gfxFloat advance = it.GetAdvance(presContext) / mFontSizeScaleFactor; |
|
4898 if (it.TextRun()->IsRightToLeft()) { |
|
4899 left = std::min(left, mPositions[index].mPosition.x - advance); |
|
4900 right = std::max(right, mPositions[index].mPosition.x); |
|
4901 } else { |
|
4902 left = std::min(left, mPositions[index].mPosition.x); |
|
4903 right = std::max(right, mPositions[index].mPosition.x + advance); |
|
4904 } |
|
4905 } |
|
4906 it.Next(); |
|
4907 index = end = it.TextElementCharIndex(); |
|
4908 } while (!it.AtEnd() && !mPositions[end].mStartOfChunk); |
|
4909 |
|
4910 if (left != std::numeric_limits<gfxFloat>::infinity()) { |
|
4911 bool isRTL = |
|
4912 chunkFrame->StyleVisibility()->mDirection == NS_STYLE_DIRECTION_RTL; |
|
4913 TextAnchorSide anchor = |
|
4914 ConvertLogicalTextAnchorToPhysical(chunkFrame->StyleSVG()->mTextAnchor, |
|
4915 isRTL); |
|
4916 |
|
4917 ShiftAnchoredChunk(mPositions, start, end, left, right, anchor); |
|
4918 } |
|
4919 |
|
4920 start = it.TextElementCharIndex(); |
|
4921 } |
|
4922 } |
|
4923 |
|
4924 void |
|
4925 SVGTextFrame::DoGlyphPositioning() |
|
4926 { |
|
4927 mPositions.Clear(); |
|
4928 RemoveStateBits(NS_STATE_SVG_POSITIONING_DIRTY); |
|
4929 |
|
4930 nsIFrame* kid = GetFirstPrincipalChild(); |
|
4931 if (kid && NS_SUBTREE_DIRTY(kid)) { |
|
4932 MOZ_ASSERT(false, "should have already reflowed the kid"); |
|
4933 return; |
|
4934 } |
|
4935 |
|
4936 // Determine the positions of each character in app units. |
|
4937 nsTArray<nsPoint> charPositions; |
|
4938 DetermineCharPositions(charPositions); |
|
4939 |
|
4940 if (charPositions.IsEmpty()) { |
|
4941 // No characters, so nothing to do. |
|
4942 return; |
|
4943 } |
|
4944 |
|
4945 // If the textLength="" attribute was specified, then we need ResolvePositions |
|
4946 // to record that a new run starts with each glyph. |
|
4947 SVGTextContentElement* element = static_cast<SVGTextContentElement*>(mContent); |
|
4948 nsSVGLength2* textLengthAttr = |
|
4949 element->GetAnimatedLength(nsGkAtoms::textLength); |
|
4950 bool adjustingTextLength = textLengthAttr->IsExplicitlySet(); |
|
4951 float expectedTextLength = textLengthAttr->GetAnimValue(element); |
|
4952 |
|
4953 if (adjustingTextLength && expectedTextLength < 0.0f) { |
|
4954 // If textLength="" is less than zero, ignore it. |
|
4955 adjustingTextLength = false; |
|
4956 } |
|
4957 |
|
4958 // Get the x, y, dx, dy, rotate values for the subtree. |
|
4959 nsTArray<gfxPoint> deltas; |
|
4960 if (!ResolvePositions(deltas, adjustingTextLength)) { |
|
4961 // If ResolvePositions returned false, it means that there were some |
|
4962 // characters in the DOM but none of them are displayed. Clear out |
|
4963 // mPositions so that we don't attempt to do any painting later. |
|
4964 mPositions.Clear(); |
|
4965 return; |
|
4966 } |
|
4967 |
|
4968 // XXX We might be able to do less work when there is at most a single |
|
4969 // x/y/dx/dy position. |
|
4970 |
|
4971 // Truncate the positioning arrays to the actual number of characters present. |
|
4972 TruncateTo(deltas, charPositions); |
|
4973 TruncateTo(mPositions, charPositions); |
|
4974 |
|
4975 // Fill in an unspecified character position at index 0. |
|
4976 if (!mPositions[0].IsXSpecified()) { |
|
4977 mPositions[0].mPosition.x = 0.0; |
|
4978 } |
|
4979 if (!mPositions[0].IsYSpecified()) { |
|
4980 mPositions[0].mPosition.y = 0.0; |
|
4981 } |
|
4982 if (!mPositions[0].IsAngleSpecified()) { |
|
4983 mPositions[0].mAngle = 0.0; |
|
4984 } |
|
4985 |
|
4986 nsPresContext* presContext = PresContext(); |
|
4987 |
|
4988 float cssPxPerDevPx = presContext-> |
|
4989 AppUnitsToFloatCSSPixels(presContext->AppUnitsPerDevPixel()); |
|
4990 double factor = cssPxPerDevPx / mFontSizeScaleFactor; |
|
4991 |
|
4992 // Determine how much to compress or expand glyph positions due to |
|
4993 // textLength="" and lengthAdjust="". |
|
4994 double adjustment = 0.0; |
|
4995 mLengthAdjustScaleFactor = 1.0f; |
|
4996 if (adjustingTextLength) { |
|
4997 nscoord frameWidth = GetFirstPrincipalChild()->GetRect().width; |
|
4998 float actualTextLength = |
|
4999 static_cast<float>(presContext->AppUnitsToGfxUnits(frameWidth) * factor); |
|
5000 |
|
5001 nsRefPtr<SVGAnimatedEnumeration> lengthAdjustEnum = element->LengthAdjust(); |
|
5002 uint16_t lengthAdjust = lengthAdjustEnum->AnimVal(); |
|
5003 switch (lengthAdjust) { |
|
5004 case SVG_LENGTHADJUST_SPACINGANDGLYPHS: |
|
5005 // Scale the glyphs and their positions. |
|
5006 if (actualTextLength > 0) { |
|
5007 mLengthAdjustScaleFactor = expectedTextLength / actualTextLength; |
|
5008 } |
|
5009 break; |
|
5010 |
|
5011 default: |
|
5012 MOZ_ASSERT(lengthAdjust == SVG_LENGTHADJUST_SPACING); |
|
5013 // Just add space between each glyph. |
|
5014 int32_t adjustableSpaces = 0; |
|
5015 for (uint32_t i = 1; i < mPositions.Length(); i++) { |
|
5016 if (!mPositions[i].mUnaddressable) { |
|
5017 adjustableSpaces++; |
|
5018 } |
|
5019 } |
|
5020 if (adjustableSpaces) { |
|
5021 adjustment = (expectedTextLength - actualTextLength) / adjustableSpaces; |
|
5022 } |
|
5023 break; |
|
5024 } |
|
5025 } |
|
5026 |
|
5027 // Fill in any unspecified character positions based on the positions recorded |
|
5028 // in charPositions, and also add in the dx/dy values. |
|
5029 if (!deltas.IsEmpty()) { |
|
5030 mPositions[0].mPosition += deltas[0]; |
|
5031 } |
|
5032 |
|
5033 for (uint32_t i = 1; i < mPositions.Length(); i++) { |
|
5034 // Fill in unspecified x position. |
|
5035 if (!mPositions[i].IsXSpecified()) { |
|
5036 nscoord d = charPositions[i].x - charPositions[i - 1].x; |
|
5037 mPositions[i].mPosition.x = |
|
5038 mPositions[i - 1].mPosition.x + |
|
5039 presContext->AppUnitsToGfxUnits(d) * factor * mLengthAdjustScaleFactor; |
|
5040 if (!mPositions[i].mUnaddressable) { |
|
5041 mPositions[i].mPosition.x += adjustment; |
|
5042 } |
|
5043 } |
|
5044 // Fill in unspecified y position. |
|
5045 if (!mPositions[i].IsYSpecified()) { |
|
5046 nscoord d = charPositions[i].y - charPositions[i - 1].y; |
|
5047 mPositions[i].mPosition.y = |
|
5048 mPositions[i - 1].mPosition.y + |
|
5049 presContext->AppUnitsToGfxUnits(d) * factor; |
|
5050 } |
|
5051 // Add in dx/dy. |
|
5052 if (i < deltas.Length()) { |
|
5053 mPositions[i].mPosition += deltas[i]; |
|
5054 } |
|
5055 // Fill in unspecified rotation values. |
|
5056 if (!mPositions[i].IsAngleSpecified()) { |
|
5057 mPositions[i].mAngle = 0.0f; |
|
5058 } |
|
5059 } |
|
5060 |
|
5061 MOZ_ASSERT(mPositions.Length() == charPositions.Length()); |
|
5062 |
|
5063 AdjustChunksForLineBreaks(); |
|
5064 AdjustPositionsForClusters(); |
|
5065 DoAnchoring(); |
|
5066 DoTextPathLayout(); |
|
5067 } |
|
5068 |
|
5069 bool |
|
5070 SVGTextFrame::ShouldRenderAsPath(nsRenderingContext* aContext, |
|
5071 nsTextFrame* aFrame, |
|
5072 bool& aShouldPaintSVGGlyphs) |
|
5073 { |
|
5074 // Rendering to a clip path. |
|
5075 if (SVGAutoRenderState::GetRenderMode(aContext) != SVGAutoRenderState::NORMAL) { |
|
5076 aShouldPaintSVGGlyphs = false; |
|
5077 return true; |
|
5078 } |
|
5079 |
|
5080 aShouldPaintSVGGlyphs = true; |
|
5081 |
|
5082 const nsStyleSVG* style = aFrame->StyleSVG(); |
|
5083 |
|
5084 // Fill is a non-solid paint, has a non-default fill-rule or has |
|
5085 // non-1 opacity. |
|
5086 if (!(style->mFill.mType == eStyleSVGPaintType_None || |
|
5087 (style->mFill.mType == eStyleSVGPaintType_Color && |
|
5088 style->mFillOpacity == 1))) { |
|
5089 return true; |
|
5090 } |
|
5091 |
|
5092 // Text has a stroke. |
|
5093 if (style->HasStroke() && |
|
5094 SVGContentUtils::CoordToFloat(PresContext(), |
|
5095 static_cast<nsSVGElement*>(mContent), |
|
5096 style->mStrokeWidth) > 0) { |
|
5097 return true; |
|
5098 } |
|
5099 |
|
5100 return false; |
|
5101 } |
|
5102 |
|
5103 void |
|
5104 SVGTextFrame::ScheduleReflowSVG() |
|
5105 { |
|
5106 if (mState & NS_FRAME_IS_NONDISPLAY) { |
|
5107 ScheduleReflowSVGNonDisplayText(); |
|
5108 } else { |
|
5109 nsSVGUtils::ScheduleReflowSVG(this); |
|
5110 } |
|
5111 } |
|
5112 |
|
5113 void |
|
5114 SVGTextFrame::NotifyGlyphMetricsChange() |
|
5115 { |
|
5116 AddStateBits(NS_STATE_SVG_POSITIONING_DIRTY); |
|
5117 nsSVGEffects::InvalidateRenderingObservers(this); |
|
5118 ScheduleReflowSVG(); |
|
5119 } |
|
5120 |
|
5121 void |
|
5122 SVGTextFrame::UpdateGlyphPositioning() |
|
5123 { |
|
5124 nsIFrame* kid = GetFirstPrincipalChild(); |
|
5125 if (!kid) { |
|
5126 return; |
|
5127 } |
|
5128 |
|
5129 if (mState & NS_STATE_SVG_POSITIONING_DIRTY) { |
|
5130 DoGlyphPositioning(); |
|
5131 } |
|
5132 } |
|
5133 |
|
5134 void |
|
5135 SVGTextFrame::MaybeReflowAnonymousBlockChild() |
|
5136 { |
|
5137 nsIFrame* kid = GetFirstPrincipalChild(); |
|
5138 if (!kid) |
|
5139 return; |
|
5140 |
|
5141 NS_ASSERTION(!(kid->GetStateBits() & NS_FRAME_IN_REFLOW), |
|
5142 "should not be in reflow when about to reflow again"); |
|
5143 |
|
5144 if (NS_SUBTREE_DIRTY(this)) { |
|
5145 if (mState & NS_FRAME_IS_DIRTY) { |
|
5146 // If we require a full reflow, ensure our kid is marked fully dirty. |
|
5147 // (Note that our anonymous nsBlockFrame is not an nsISVGChildFrame, so |
|
5148 // even when we are called via our ReflowSVG this will not be done for us |
|
5149 // by nsSVGDisplayContainerFrame::ReflowSVG.) |
|
5150 kid->AddStateBits(NS_FRAME_IS_DIRTY); |
|
5151 } |
|
5152 MOZ_ASSERT(nsSVGUtils::AnyOuterSVGIsCallingReflowSVG(this), |
|
5153 "should be under ReflowSVG"); |
|
5154 nsPresContext::InterruptPreventer noInterrupts(PresContext()); |
|
5155 DoReflow(); |
|
5156 } |
|
5157 } |
|
5158 |
|
5159 void |
|
5160 SVGTextFrame::DoReflow() |
|
5161 { |
|
5162 // Since we are going to reflow the anonymous block frame, we will |
|
5163 // need to update mPositions. |
|
5164 AddStateBits(NS_STATE_SVG_POSITIONING_DIRTY); |
|
5165 |
|
5166 if (mState & NS_FRAME_IS_NONDISPLAY) { |
|
5167 // Normally, these dirty flags would be cleared in ReflowSVG(), but that |
|
5168 // doesn't get called for non-display frames. We don't want to reflow our |
|
5169 // descendants every time SVGTextFrame::PaintSVG makes sure that we have |
|
5170 // valid positions by calling UpdateGlyphPositioning(), so we need to clear |
|
5171 // these dirty bits. Note that this also breaks an invalidation loop where |
|
5172 // our descendants invalidate as they reflow, which invalidates rendering |
|
5173 // observers, which reschedules the frame that is currently painting by |
|
5174 // referencing us to paint again. See bug 839958 comment 7. Hopefully we |
|
5175 // will break that loop more convincingly at some point. |
|
5176 mState &= ~(NS_FRAME_IS_DIRTY | NS_FRAME_HAS_DIRTY_CHILDREN); |
|
5177 } |
|
5178 |
|
5179 nsPresContext *presContext = PresContext(); |
|
5180 nsIFrame* kid = GetFirstPrincipalChild(); |
|
5181 if (!kid) |
|
5182 return; |
|
5183 |
|
5184 nsRefPtr<nsRenderingContext> renderingContext = |
|
5185 presContext->PresShell()->CreateReferenceRenderingContext(); |
|
5186 |
|
5187 if (UpdateFontSizeScaleFactor()) { |
|
5188 // If the font size scale factor changed, we need the block to report |
|
5189 // an updated preferred width. |
|
5190 kid->MarkIntrinsicWidthsDirty(); |
|
5191 } |
|
5192 |
|
5193 mState |= NS_STATE_SVG_TEXT_IN_REFLOW; |
|
5194 |
|
5195 nscoord width = kid->GetPrefWidth(renderingContext); |
|
5196 nsHTMLReflowState reflowState(presContext, kid, |
|
5197 renderingContext, |
|
5198 nsSize(width, NS_UNCONSTRAINEDSIZE)); |
|
5199 nsHTMLReflowMetrics desiredSize(reflowState); |
|
5200 nsReflowStatus status; |
|
5201 |
|
5202 NS_ASSERTION(reflowState.ComputedPhysicalBorderPadding() == nsMargin(0, 0, 0, 0) && |
|
5203 reflowState.ComputedPhysicalMargin() == nsMargin(0, 0, 0, 0), |
|
5204 "style system should ensure that :-moz-svg-text " |
|
5205 "does not get styled"); |
|
5206 |
|
5207 kid->WillReflow(presContext); |
|
5208 kid->Reflow(presContext, desiredSize, reflowState, status); |
|
5209 kid->DidReflow(presContext, &reflowState, nsDidReflowStatus::FINISHED); |
|
5210 kid->SetSize(nsSize(desiredSize.Width(), desiredSize.Height())); |
|
5211 |
|
5212 mState &= ~NS_STATE_SVG_TEXT_IN_REFLOW; |
|
5213 |
|
5214 TextNodeCorrespondenceRecorder::RecordCorrespondence(this); |
|
5215 } |
|
5216 |
|
5217 // Usable font size range in devpixels / user-units |
|
5218 #define CLAMP_MIN_SIZE 8.0 |
|
5219 #define CLAMP_MAX_SIZE 200.0 |
|
5220 #define PRECISE_SIZE 200.0 |
|
5221 |
|
5222 bool |
|
5223 SVGTextFrame::UpdateFontSizeScaleFactor() |
|
5224 { |
|
5225 double oldFontSizeScaleFactor = mFontSizeScaleFactor; |
|
5226 |
|
5227 nsPresContext* presContext = PresContext(); |
|
5228 |
|
5229 bool geometricPrecision = false; |
|
5230 nscoord min = nscoord_MAX, |
|
5231 max = nscoord_MIN; |
|
5232 |
|
5233 // Find the minimum and maximum font sizes used over all the |
|
5234 // nsTextFrames. |
|
5235 TextFrameIterator it(this); |
|
5236 nsTextFrame* f = it.Current(); |
|
5237 while (f) { |
|
5238 if (!geometricPrecision) { |
|
5239 // Unfortunately we can't treat text-rendering:geometricPrecision |
|
5240 // separately for each text frame. |
|
5241 geometricPrecision = f->StyleSVG()->mTextRendering == |
|
5242 NS_STYLE_TEXT_RENDERING_GEOMETRICPRECISION; |
|
5243 } |
|
5244 nscoord size = f->StyleFont()->mFont.size; |
|
5245 if (size) { |
|
5246 min = std::min(min, size); |
|
5247 max = std::max(max, size); |
|
5248 } |
|
5249 f = it.Next(); |
|
5250 } |
|
5251 |
|
5252 if (min == nscoord_MAX) { |
|
5253 // No text, so no need for scaling. |
|
5254 mFontSizeScaleFactor = 1.0; |
|
5255 return mFontSizeScaleFactor != oldFontSizeScaleFactor; |
|
5256 } |
|
5257 |
|
5258 double minSize = presContext->AppUnitsToFloatCSSPixels(min); |
|
5259 |
|
5260 if (geometricPrecision) { |
|
5261 // We want to ensure minSize is scaled to PRECISE_SIZE. |
|
5262 mFontSizeScaleFactor = PRECISE_SIZE / minSize; |
|
5263 return mFontSizeScaleFactor != oldFontSizeScaleFactor; |
|
5264 } |
|
5265 |
|
5266 // When we are non-display, we could be painted in different coordinate |
|
5267 // spaces, and we don't want to have to reflow for each of these. We |
|
5268 // just assume that the context scale is 1.0 for them all, so we don't |
|
5269 // get stuck with a font size scale factor based on whichever referencing |
|
5270 // frame happens to reflow first. |
|
5271 double contextScale = 1.0; |
|
5272 if (!(mState & NS_FRAME_IS_NONDISPLAY)) { |
|
5273 gfxMatrix m(GetCanvasTM(FOR_OUTERSVG_TM)); |
|
5274 if (!m.IsSingular()) { |
|
5275 contextScale = GetContextScale(m); |
|
5276 } |
|
5277 } |
|
5278 mLastContextScale = contextScale; |
|
5279 |
|
5280 double maxSize = presContext->AppUnitsToFloatCSSPixels(max); |
|
5281 |
|
5282 // But we want to ignore any scaling required due to HiDPI displays, since |
|
5283 // regular CSS text frames will still create text runs using the font size |
|
5284 // in CSS pixels, and we want SVG text to have the same rendering as HTML |
|
5285 // text for regular font sizes. |
|
5286 float cssPxPerDevPx = |
|
5287 presContext->AppUnitsToFloatCSSPixels(presContext->AppUnitsPerDevPixel()); |
|
5288 contextScale *= cssPxPerDevPx; |
|
5289 |
|
5290 double minTextRunSize = minSize * contextScale; |
|
5291 double maxTextRunSize = maxSize * contextScale; |
|
5292 |
|
5293 if (minTextRunSize >= CLAMP_MIN_SIZE && |
|
5294 maxTextRunSize <= CLAMP_MAX_SIZE) { |
|
5295 // We are already in the ideal font size range for all text frames, |
|
5296 // so we only have to take into account the contextScale. |
|
5297 mFontSizeScaleFactor = contextScale; |
|
5298 } else if (maxSize / minSize > CLAMP_MAX_SIZE / CLAMP_MIN_SIZE) { |
|
5299 // We can't scale the font sizes so that all of the text frames lie |
|
5300 // within our ideal font size range, so we treat the minimum as more |
|
5301 // important and just scale so that minSize = CLAMP_MIN_SIZE. |
|
5302 mFontSizeScaleFactor = CLAMP_MIN_SIZE / minTextRunSize; |
|
5303 } else if (minTextRunSize < CLAMP_MIN_SIZE) { |
|
5304 mFontSizeScaleFactor = CLAMP_MIN_SIZE / minTextRunSize; |
|
5305 } else { |
|
5306 mFontSizeScaleFactor = CLAMP_MAX_SIZE / maxTextRunSize; |
|
5307 } |
|
5308 |
|
5309 return mFontSizeScaleFactor != oldFontSizeScaleFactor; |
|
5310 } |
|
5311 |
|
5312 double |
|
5313 SVGTextFrame::GetFontSizeScaleFactor() const |
|
5314 { |
|
5315 return mFontSizeScaleFactor; |
|
5316 } |
|
5317 |
|
5318 /** |
|
5319 * Take aPoint, which is in the <text> element's user space, and convert |
|
5320 * it to the appropriate frame user space of aChildFrame according to |
|
5321 * which rendered run the point hits. |
|
5322 */ |
|
5323 gfxPoint |
|
5324 SVGTextFrame::TransformFramePointToTextChild(const gfxPoint& aPoint, |
|
5325 nsIFrame* aChildFrame) |
|
5326 { |
|
5327 NS_ASSERTION(aChildFrame && |
|
5328 nsLayoutUtils::GetClosestFrameOfType |
|
5329 (aChildFrame->GetParent(), nsGkAtoms::svgTextFrame) == this, |
|
5330 "aChildFrame must be a descendant of this frame"); |
|
5331 |
|
5332 UpdateGlyphPositioning(); |
|
5333 |
|
5334 nsPresContext* presContext = PresContext(); |
|
5335 |
|
5336 // Add in the mRect offset to aPoint, as that will have been taken into |
|
5337 // account when transforming the point from the ancestor frame down |
|
5338 // to this one. |
|
5339 float cssPxPerDevPx = presContext-> |
|
5340 AppUnitsToFloatCSSPixels(presContext->AppUnitsPerDevPixel()); |
|
5341 float factor = presContext->AppUnitsPerCSSPixel(); |
|
5342 gfxPoint framePosition(NSAppUnitsToFloatPixels(mRect.x, factor), |
|
5343 NSAppUnitsToFloatPixels(mRect.y, factor)); |
|
5344 gfxPoint pointInUserSpace = aPoint * cssPxPerDevPx + framePosition; |
|
5345 |
|
5346 // Find the closest rendered run for the text frames beneath aChildFrame. |
|
5347 TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames, |
|
5348 aChildFrame); |
|
5349 TextRenderedRun hit; |
|
5350 gfxPoint pointInRun; |
|
5351 nscoord dx = nscoord_MAX; |
|
5352 nscoord dy = nscoord_MAX; |
|
5353 for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { |
|
5354 uint32_t flags = TextRenderedRun::eIncludeFill | |
|
5355 TextRenderedRun::eIncludeStroke | |
|
5356 TextRenderedRun::eNoHorizontalOverflow; |
|
5357 gfxRect runRect = run.GetRunUserSpaceRect(presContext, flags).ToThebesRect(); |
|
5358 |
|
5359 gfxPoint pointInRunUserSpace = |
|
5360 run.GetTransformFromRunUserSpaceToUserSpace(presContext).Invert(). |
|
5361 Transform(pointInUserSpace); |
|
5362 |
|
5363 if (Inside(runRect, pointInRunUserSpace)) { |
|
5364 // The point was inside the rendered run's rect, so we choose it. |
|
5365 dx = 0; |
|
5366 dy = 0; |
|
5367 pointInRun = pointInRunUserSpace; |
|
5368 hit = run; |
|
5369 } else if (nsLayoutUtils::PointIsCloserToRect(pointInRunUserSpace, |
|
5370 runRect, dx, dy)) { |
|
5371 // The point was closer to this rendered run's rect than any others |
|
5372 // we've seen so far. |
|
5373 pointInRun.x = clamped(pointInRunUserSpace.x, |
|
5374 runRect.X(), runRect.XMost()); |
|
5375 pointInRun.y = clamped(pointInRunUserSpace.y, |
|
5376 runRect.Y(), runRect.YMost()); |
|
5377 hit = run; |
|
5378 } |
|
5379 } |
|
5380 |
|
5381 if (!hit.mFrame) { |
|
5382 // We didn't find any rendered runs for the frame. |
|
5383 return aPoint; |
|
5384 } |
|
5385 |
|
5386 // Return the point in user units relative to the nsTextFrame, |
|
5387 // but taking into account mFontSizeScaleFactor. |
|
5388 gfxMatrix m = hit.GetTransformFromRunUserSpaceToFrameUserSpace(presContext); |
|
5389 m.Scale(mFontSizeScaleFactor, mFontSizeScaleFactor); |
|
5390 return m.Transform(pointInRun) / cssPxPerDevPx; |
|
5391 } |
|
5392 |
|
5393 /** |
|
5394 * For each rendered run for frames beneath aChildFrame, convert aRect |
|
5395 * into the run's frame user space and intersect it with the run's |
|
5396 * frame user space rectangle. For each of these intersections, |
|
5397 * then translate them up into aChildFrame's coordinate space |
|
5398 * and union them all together. |
|
5399 */ |
|
5400 gfxRect |
|
5401 SVGTextFrame::TransformFrameRectToTextChild(const gfxRect& aRect, |
|
5402 nsIFrame* aChildFrame) |
|
5403 { |
|
5404 NS_ASSERTION(aChildFrame && |
|
5405 nsLayoutUtils::GetClosestFrameOfType |
|
5406 (aChildFrame->GetParent(), nsGkAtoms::svgTextFrame) == this, |
|
5407 "aChildFrame must be a descendant of this frame"); |
|
5408 |
|
5409 UpdateGlyphPositioning(); |
|
5410 |
|
5411 nsPresContext* presContext = PresContext(); |
|
5412 |
|
5413 // Add in the mRect offset to aRect, as that will have been taken into |
|
5414 // account when transforming the rect from the ancestor frame down |
|
5415 // to this one. |
|
5416 float cssPxPerDevPx = presContext-> |
|
5417 AppUnitsToFloatCSSPixels(presContext->AppUnitsPerDevPixel()); |
|
5418 float factor = presContext->AppUnitsPerCSSPixel(); |
|
5419 gfxPoint framePosition(NSAppUnitsToFloatPixels(mRect.x, factor), |
|
5420 NSAppUnitsToFloatPixels(mRect.y, factor)); |
|
5421 gfxRect incomingRectInUserSpace(aRect.x * cssPxPerDevPx + framePosition.x, |
|
5422 aRect.y * cssPxPerDevPx + framePosition.y, |
|
5423 aRect.width * cssPxPerDevPx, |
|
5424 aRect.height * cssPxPerDevPx); |
|
5425 |
|
5426 // Find each rendered run for text frames beneath aChildFrame. |
|
5427 gfxRect result; |
|
5428 TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames, |
|
5429 aChildFrame); |
|
5430 for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { |
|
5431 // Convert the incoming rect into frame user space. |
|
5432 gfxMatrix m; |
|
5433 m.PreMultiply(run.GetTransformFromRunUserSpaceToUserSpace(presContext).Invert()); |
|
5434 m.PreMultiply(run.GetTransformFromRunUserSpaceToFrameUserSpace(presContext)); |
|
5435 gfxRect incomingRectInFrameUserSpace = |
|
5436 m.TransformBounds(incomingRectInUserSpace); |
|
5437 |
|
5438 // Intersect it with this run's rectangle. |
|
5439 uint32_t flags = TextRenderedRun::eIncludeFill | |
|
5440 TextRenderedRun::eIncludeStroke; |
|
5441 SVGBBox runRectInFrameUserSpace = run.GetFrameUserSpaceRect(presContext, flags); |
|
5442 if (runRectInFrameUserSpace.IsEmpty()) { |
|
5443 continue; |
|
5444 } |
|
5445 gfxRect runIntersectionInFrameUserSpace = |
|
5446 incomingRectInFrameUserSpace.Intersect(runRectInFrameUserSpace.ToThebesRect()); |
|
5447 |
|
5448 if (!runIntersectionInFrameUserSpace.IsEmpty()) { |
|
5449 // Take the font size scale into account. |
|
5450 runIntersectionInFrameUserSpace.x *= mFontSizeScaleFactor; |
|
5451 runIntersectionInFrameUserSpace.y *= mFontSizeScaleFactor; |
|
5452 runIntersectionInFrameUserSpace.width *= mFontSizeScaleFactor; |
|
5453 runIntersectionInFrameUserSpace.height *= mFontSizeScaleFactor; |
|
5454 |
|
5455 // Convert it into the coordinate space of aChildFrame. |
|
5456 nsPoint offset = run.mFrame->GetOffsetTo(aChildFrame); |
|
5457 gfxRect runIntersection = |
|
5458 runIntersectionInFrameUserSpace + |
|
5459 gfxPoint(NSAppUnitsToFloatPixels(offset.x, factor), |
|
5460 NSAppUnitsToFloatPixels(offset.y, factor)); |
|
5461 |
|
5462 // Union it into the result. |
|
5463 result.UnionRect(result, runIntersection); |
|
5464 } |
|
5465 } |
|
5466 |
|
5467 return result; |
|
5468 } |
|
5469 |
|
5470 /** |
|
5471 * For each rendered run beneath aChildFrame, translate aRect from |
|
5472 * aChildFrame to the run's text frame, transform it then into |
|
5473 * the run's frame user space, intersect it with the run's |
|
5474 * frame user space rect, then transform it up to user space. |
|
5475 * The result is the union of all of these. |
|
5476 */ |
|
5477 gfxRect |
|
5478 SVGTextFrame::TransformFrameRectFromTextChild(const nsRect& aRect, |
|
5479 nsIFrame* aChildFrame) |
|
5480 { |
|
5481 NS_ASSERTION(aChildFrame && |
|
5482 nsLayoutUtils::GetClosestFrameOfType |
|
5483 (aChildFrame->GetParent(), nsGkAtoms::svgTextFrame) == this, |
|
5484 "aChildFrame must be a descendant of this frame"); |
|
5485 |
|
5486 UpdateGlyphPositioning(); |
|
5487 |
|
5488 nsPresContext* presContext = PresContext(); |
|
5489 |
|
5490 gfxRect result; |
|
5491 TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames, |
|
5492 aChildFrame); |
|
5493 for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) { |
|
5494 // First, translate aRect from aChildFrame to this run's frame. |
|
5495 nsRect rectInTextFrame = aRect + aChildFrame->GetOffsetTo(run.mFrame); |
|
5496 |
|
5497 // Scale it into frame user space. |
|
5498 gfxRect rectInFrameUserSpace = |
|
5499 AppUnitsToFloatCSSPixels(gfxRect(rectInTextFrame.x, |
|
5500 rectInTextFrame.y, |
|
5501 rectInTextFrame.width, |
|
5502 rectInTextFrame.height), presContext); |
|
5503 |
|
5504 // Intersect it with the run. |
|
5505 uint32_t flags = TextRenderedRun::eIncludeFill | |
|
5506 TextRenderedRun::eIncludeStroke; |
|
5507 rectInFrameUserSpace.IntersectRect |
|
5508 (rectInFrameUserSpace, run.GetFrameUserSpaceRect(presContext, flags).ToThebesRect()); |
|
5509 |
|
5510 if (!rectInFrameUserSpace.IsEmpty()) { |
|
5511 // Transform it up to user space of the <text>, also taking into |
|
5512 // account the font size scale. |
|
5513 gfxMatrix m = run.GetTransformFromRunUserSpaceToUserSpace(presContext); |
|
5514 m.Scale(mFontSizeScaleFactor, mFontSizeScaleFactor); |
|
5515 gfxRect rectInUserSpace = m.Transform(rectInFrameUserSpace); |
|
5516 |
|
5517 // Union it into the result. |
|
5518 result.UnionRect(result, rectInUserSpace); |
|
5519 } |
|
5520 } |
|
5521 |
|
5522 // Subtract the mRect offset from the result, as our user space for |
|
5523 // this frame is relative to the top-left of mRect. |
|
5524 float factor = presContext->AppUnitsPerCSSPixel(); |
|
5525 gfxPoint framePosition(NSAppUnitsToFloatPixels(mRect.x, factor), |
|
5526 NSAppUnitsToFloatPixels(mRect.y, factor)); |
|
5527 |
|
5528 return result - framePosition; |
|
5529 } |
|
5530 |
|
5531 DrawMode |
|
5532 SVGTextFrame::SetupCairoState(gfxContext* aContext, |
|
5533 nsIFrame* aFrame, |
|
5534 gfxTextContextPaint* aOuterContextPaint, |
|
5535 gfxTextContextPaint** aThisContextPaint) |
|
5536 { |
|
5537 DrawMode toDraw = DrawMode(0); |
|
5538 SVGTextContextPaint *thisContextPaint = new SVGTextContextPaint(); |
|
5539 |
|
5540 if (SetupCairoStroke(aContext, aFrame, aOuterContextPaint, thisContextPaint)) { |
|
5541 toDraw = DrawMode(int(toDraw) | int(DrawMode::GLYPH_STROKE)); |
|
5542 } |
|
5543 |
|
5544 if (SetupCairoFill(aContext, aFrame, aOuterContextPaint, thisContextPaint)) { |
|
5545 toDraw = DrawMode(int(toDraw) | int(DrawMode::GLYPH_FILL)); |
|
5546 } |
|
5547 |
|
5548 *aThisContextPaint = thisContextPaint; |
|
5549 |
|
5550 return toDraw; |
|
5551 } |
|
5552 |
|
5553 bool |
|
5554 SVGTextFrame::SetupCairoStroke(gfxContext* aContext, |
|
5555 nsIFrame* aFrame, |
|
5556 gfxTextContextPaint* aOuterContextPaint, |
|
5557 SVGTextContextPaint* aThisContextPaint) |
|
5558 { |
|
5559 const nsStyleSVG *style = aFrame->StyleSVG(); |
|
5560 if (style->mStroke.mType == eStyleSVGPaintType_None) { |
|
5561 aThisContextPaint->SetStrokeOpacity(0.0f); |
|
5562 return false; |
|
5563 } |
|
5564 |
|
5565 nsSVGUtils::SetupCairoStrokeGeometry(aFrame, aContext, aOuterContextPaint); |
|
5566 float opacity = nsSVGUtils::GetOpacity(style->mStrokeOpacitySource, |
|
5567 style->mStrokeOpacity, |
|
5568 aOuterContextPaint); |
|
5569 |
|
5570 SetupInheritablePaint(aContext, aFrame, opacity, aOuterContextPaint, |
|
5571 aThisContextPaint->mStrokePaint, &nsStyleSVG::mStroke, |
|
5572 nsSVGEffects::StrokeProperty()); |
|
5573 |
|
5574 aThisContextPaint->SetStrokeOpacity(opacity); |
|
5575 |
|
5576 return opacity != 0.0f; |
|
5577 } |
|
5578 |
|
5579 bool |
|
5580 SVGTextFrame::SetupCairoFill(gfxContext* aContext, |
|
5581 nsIFrame* aFrame, |
|
5582 gfxTextContextPaint* aOuterContextPaint, |
|
5583 SVGTextContextPaint* aThisContextPaint) |
|
5584 { |
|
5585 const nsStyleSVG *style = aFrame->StyleSVG(); |
|
5586 if (style->mFill.mType == eStyleSVGPaintType_None) { |
|
5587 aThisContextPaint->SetFillOpacity(0.0f); |
|
5588 return false; |
|
5589 } |
|
5590 |
|
5591 float opacity = nsSVGUtils::GetOpacity(style->mFillOpacitySource, |
|
5592 style->mFillOpacity, |
|
5593 aOuterContextPaint); |
|
5594 |
|
5595 SetupInheritablePaint(aContext, aFrame, opacity, aOuterContextPaint, |
|
5596 aThisContextPaint->mFillPaint, &nsStyleSVG::mFill, |
|
5597 nsSVGEffects::FillProperty()); |
|
5598 |
|
5599 aThisContextPaint->SetFillOpacity(opacity); |
|
5600 |
|
5601 return true; |
|
5602 } |
|
5603 |
|
5604 void |
|
5605 SVGTextFrame::SetupInheritablePaint(gfxContext* aContext, |
|
5606 nsIFrame* aFrame, |
|
5607 float& aOpacity, |
|
5608 gfxTextContextPaint* aOuterContextPaint, |
|
5609 SVGTextContextPaint::Paint& aTargetPaint, |
|
5610 nsStyleSVGPaint nsStyleSVG::*aFillOrStroke, |
|
5611 const FramePropertyDescriptor* aProperty) |
|
5612 { |
|
5613 const nsStyleSVG *style = aFrame->StyleSVG(); |
|
5614 nsSVGPaintServerFrame *ps = |
|
5615 nsSVGEffects::GetPaintServer(aFrame, &(style->*aFillOrStroke), aProperty); |
|
5616 |
|
5617 if (ps && ps->SetupPaintServer(aContext, aFrame, aFillOrStroke, aOpacity)) { |
|
5618 aTargetPaint.SetPaintServer(aFrame, aContext->CurrentMatrix(), ps); |
|
5619 } else if (nsSVGUtils::SetupContextPaint(aContext, aOuterContextPaint, |
|
5620 style->*aFillOrStroke, |
|
5621 aOpacity)) { |
|
5622 aTargetPaint.SetContextPaint(aOuterContextPaint, (style->*aFillOrStroke).mType); |
|
5623 } else { |
|
5624 nscolor color = nsSVGUtils::GetFallbackOrPaintColor(aContext, |
|
5625 aFrame->StyleContext(), |
|
5626 aFillOrStroke); |
|
5627 aTargetPaint.SetColor(color); |
|
5628 |
|
5629 nsRefPtr<gfxPattern> pattern = |
|
5630 new gfxPattern(gfxRGBA(NS_GET_R(color) / 255.0, |
|
5631 NS_GET_G(color) / 255.0, |
|
5632 NS_GET_B(color) / 255.0, |
|
5633 NS_GET_A(color) / 255.0 * aOpacity)); |
|
5634 aContext->SetPattern(pattern); |
|
5635 } |
|
5636 } |