Bug 1330236 - Compute SVG getNumberOfChars() quicker in simple cases. r=longsonr
MozReview-Commit-ID: FgwR9dxTefx
--- a/dom/svg/SVGTextContentElement.cpp
+++ b/dom/svg/SVGTextContentElement.cpp
@@ -1,18 +1,23 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "mozilla/dom/SVGTextContentElement.h"
+
+#include "mozilla/dom/SVGIRect.h"
+#include "nsBidiUtils.h"
#include "nsISVGPoint.h"
+#include "nsTextFragment.h"
+#include "nsTextFrameUtils.h"
+#include "nsTextNode.h"
#include "SVGTextFrame.h"
-#include "mozilla/dom/SVGIRect.h"
namespace mozilla {
namespace dom {
nsSVGEnumMapping SVGTextContentElement::sLengthAdjustMap[] = {
{ &nsGkAtoms::spacing, SVG_LENGTHADJUST_SPACING },
{ &nsGkAtoms::spacingAndGlyphs, SVG_LENGTHADJUST_SPACINGANDGLYPHS },
{ nullptr, 0 }
@@ -27,43 +32,100 @@ nsSVGElement::LengthInfo SVGTextContentE
{
{ &nsGkAtoms::textLength, 0, nsIDOMSVGLength::SVG_LENGTHTYPE_NUMBER, SVGContentUtils::XY }
};
SVGTextFrame*
SVGTextContentElement::GetSVGTextFrame()
{
nsIFrame* frame = GetPrimaryFrame(FlushType::Layout);
- while (frame) {
- SVGTextFrame* textFrame = do_QueryFrame(frame);
- if (textFrame) {
- return textFrame;
- }
- frame = frame->GetParent();
- }
- return nullptr;
+ nsIFrame* textFrame =
+ nsLayoutUtils::GetClosestFrameOfType(frame, nsGkAtoms::svgTextFrame);
+ return static_cast<SVGTextFrame*>(textFrame);
+}
+
+SVGTextFrame*
+SVGTextContentElement::GetSVGTextFrameForNonLayoutDependentQuery()
+{
+ nsIFrame* frame = GetPrimaryFrame(FlushType::Frames);
+ nsIFrame* textFrame =
+ nsLayoutUtils::GetClosestFrameOfType(frame, nsGkAtoms::svgTextFrame);
+ return static_cast<SVGTextFrame*>(textFrame);
}
already_AddRefed<SVGAnimatedLength>
SVGTextContentElement::TextLength()
{
return LengthAttributes()[TEXTLENGTH].ToDOMAnimatedLength(this);
}
already_AddRefed<SVGAnimatedEnumeration>
SVGTextContentElement::LengthAdjust()
{
return EnumAttributes()[LENGTHADJUST].ToDOMAnimatedEnum(this);
}
//----------------------------------------------------------------------
+template<typename T>
+static bool
+FragmentHasSkippableCharacter(const T* aBuffer, uint32_t aLength)
+{
+ for (uint32_t i = 0; i < aLength; i++) {
+ if (nsTextFrameUtils::IsSkippableCharacterForTransformText(aBuffer[i])) {
+ return true;
+ }
+ }
+ return false;
+}
+
+Maybe<int32_t>
+SVGTextContentElement::GetNonLayoutDependentNumberOfChars()
+{
+ SVGTextFrame* frame = GetSVGTextFrameForNonLayoutDependentQuery();
+ if (!frame || frame != GetPrimaryFrame()) {
+ // Only support this fast path on <text>, not child <tspan>s, etc.
+ return Some(0);
+ }
+
+ uint32_t num = 0;
+
+ for (nsINode* n = Element::GetFirstChild(); n; n = n->GetNextSibling()) {
+ if (!n->IsNodeOfType(nsINode::eTEXT)) {
+ return Nothing();
+ }
+
+ const nsTextFragment* text = static_cast<nsTextNode*>(n)->GetText();
+ uint32_t length = text->GetLength();
+
+ if (text->Is2b()) {
+ if (FragmentHasSkippableCharacter(text->Get2b(), length)) {
+ return Nothing();
+ }
+ } else {
+ auto buffer = reinterpret_cast<const uint8_t*>(text->Get1b());
+ if (FragmentHasSkippableCharacter(buffer, length)) {
+ return Nothing();
+ }
+ }
+
+ num += length;
+ }
+
+ return Some(num);
+}
+
int32_t
SVGTextContentElement::GetNumberOfChars()
{
+ Maybe<int32_t> num = GetNonLayoutDependentNumberOfChars();
+ if (num) {
+ return *num;
+ }
+
SVGTextFrame* textFrame = GetSVGTextFrame();
return textFrame ? textFrame->GetNumberOfChars(this) : 0;
}
float
SVGTextContentElement::GetComputedTextLength()
{
SVGTextFrame* textFrame = GetSVGTextFrame();
--- a/dom/svg/SVGTextContentElement.h
+++ b/dom/svg/SVGTextContentElement.h
@@ -47,16 +47,18 @@ public:
protected:
explicit SVGTextContentElement(already_AddRefed<mozilla::dom::NodeInfo>& aNodeInfo)
: SVGTextContentElementBase(aNodeInfo)
{}
SVGTextFrame* GetSVGTextFrame();
+ SVGTextFrame* GetSVGTextFrameForNonLayoutDependentQuery();
+ mozilla::Maybe<int32_t> GetNonLayoutDependentNumberOfChars();
enum { LENGTHADJUST };
virtual nsSVGEnum* EnumAttributes() = 0;
static nsSVGEnumMapping sLengthAdjustMap[];
static EnumInfo sEnumInfo[1];
enum { TEXTLENGTH };
virtual nsSVGLength2* LengthAttributes() = 0;
--- a/layout/generic/nsTextFrameUtils.cpp
+++ b/layout/generic/nsTextFrameUtils.cpp
@@ -51,16 +51,46 @@ IsSpaceOrTab(char16_t aCh)
}
static bool
IsSpaceOrTabOrSegmentBreak(char16_t aCh)
{
return IsSpaceOrTab(aCh) || IsSegmentBreak(aCh);
}
+template<typename CharT>
+/* static */ bool
+nsTextFrameUtils::IsSkippableCharacterForTransformText(CharT aChar)
+{
+ return aChar == ' ' ||
+ aChar == '\t' ||
+ aChar == '\n' ||
+ aChar == CH_SHY ||
+ (aChar > 0xFF && IsBidiControl(aChar));
+}
+
+#ifdef DEBUG
+template<typename CharT>
+static void AssertSkippedExpectedChars(const CharT* aText,
+ const gfxSkipChars& aSkipChars,
+ int32_t aSkipCharsOffset)
+{
+ gfxSkipCharsIterator it(aSkipChars);
+ it.AdvanceOriginal(aSkipCharsOffset);
+ while (it.GetOriginalOffset() < it.GetOriginalEnd()) {
+ CharT ch = aText[it.GetOriginalOffset() - aSkipCharsOffset];
+ MOZ_ASSERT(!it.IsOriginalCharSkipped() ||
+ nsTextFrameUtils::IsSkippableCharacterForTransformText(ch),
+ "skipped unexpected character; need to update "
+ "IsSkippableCharacterForTransformText?");
+ it.AdvanceOriginal(1);
+ }
+}
+#endif
+
template<class CharT>
static CharT*
TransformWhiteSpaces(const CharT* aText, uint32_t aLength,
uint32_t aBegin, uint32_t aEnd,
bool aHasSegmentBreak,
bool& aInWhitespace,
CharT* aOutput,
uint32_t& aFlags,
@@ -178,16 +208,19 @@ nsTextFrameUtils::TransformText(const Ch
CharT* aOutput,
CompressionMode aCompression,
uint8_t* aIncomingFlags,
gfxSkipChars* aSkipChars,
uint32_t* aAnalysisFlags)
{
uint32_t flags = 0;
CharT* outputStart = aOutput;
+#ifdef DEBUG
+ int32_t skipCharsOffset = aSkipChars->GetOriginalCharCount();
+#endif
bool lastCharArabic = false;
if (aCompression == COMPRESS_NONE ||
aCompression == COMPRESS_NONE_TRANSFORM_TO_SPACE) {
// Skip discardables.
uint32_t i;
for (i = 0; i < aLength; ++i) {
CharT ch = aText[i];
@@ -298,22 +331,28 @@ nsTextFrameUtils::TransformText(const Ch
*aIncomingFlags &= ~INCOMING_WHITESPACE;
}
}
if (outputStart + aLength != aOutput) {
flags |= TEXT_WAS_TRANSFORMED;
}
*aAnalysisFlags = flags;
+
+#ifdef DEBUG
+ AssertSkippedExpectedChars(aText, *aSkipChars, skipCharsOffset);
+#endif
return aOutput;
}
+
/*
- * NOTE: This template is part of public API of nsTextFrameUtils, while
- * its function body is not available in the header. It may stop working
- * (fail to resolve symbol in link time) once its callsites are moved to a
+ * NOTE: The TransformText and IsSkippableCharacterForTransformText template
+ * functions are part of the public API of nsTextFrameUtils, while
+ * their function bodies are not available in the header. They may stop working
+ * (fail to resolve symbol in link time) once their callsites are moved to a
* different translation unit (e.g. a different unified source file).
* Explicit instantiating this function template with `uint8_t` and `char16_t`
* could prevent us from the potential risk.
*/
template uint8_t*
nsTextFrameUtils::TransformText(const uint8_t* aText, uint32_t aLength,
uint8_t* aOutput,
CompressionMode aCompression,
@@ -322,16 +361,20 @@ nsTextFrameUtils::TransformText(const ui
uint32_t* aAnalysisFlags);
template char16_t*
nsTextFrameUtils::TransformText(const char16_t* aText, uint32_t aLength,
char16_t* aOutput,
CompressionMode aCompression,
uint8_t* aIncomingFlags,
gfxSkipChars* aSkipChars,
uint32_t* aAnalysisFlags);
+template bool
+nsTextFrameUtils::IsSkippableCharacterForTransformText(uint8_t aChar);
+template bool
+nsTextFrameUtils::IsSkippableCharacterForTransformText(char16_t aChar);
uint32_t
nsTextFrameUtils::ComputeApproximateLengthWithWhitespaceCompression(
nsIContent *aContent, const nsStyleText *aStyleText)
{
const nsTextFragment *frag = aContent->GetText();
// This is an approximation so we don't really need anything
// too fancy here.
--- a/layout/generic/nsTextFrameUtils.h
+++ b/layout/generic/nsTextFrameUtils.h
@@ -116,16 +116,26 @@ public:
template<class CharT>
static CharT* TransformText(const CharT* aText, uint32_t aLength,
CharT* aOutput,
CompressionMode aCompression,
uint8_t* aIncomingFlags,
gfxSkipChars* aSkipChars,
uint32_t* aAnalysisFlags);
+ /**
+ * Returns whether aChar is a character that nsTextFrameUtils::TransformText
+ * might mark as skipped. This is used by
+ * SVGTextContentElement::GetNumberOfChars to know whether reflowing frames,
+ * so that we have the results of TransformText, is required, or whether we
+ * can use a fast path instead.
+ */
+ template<class CharT>
+ static bool IsSkippableCharacterForTransformText(CharT aChar);
+
static void
AppendLineBreakOffset(nsTArray<uint32_t>* aArray, uint32_t aOffset)
{
if (aArray->Length() > 0 && (*aArray)[aArray->Length() - 1] == aOffset)
return;
aArray->AppendElement(aOffset);
}