layout/svg/SVGTextFrame.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial