Wed, 31 Dec 2014 06:09:35 +0100
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 | } |