Bug 842119. r=longsonr

This commit is contained in:
Cameron McCormack 2013-02-19 13:50:05 +11:00
parent e93adf3b70
commit bde6208078
4 changed files with 227 additions and 72 deletions

View File

@ -73,6 +73,7 @@ MOCHITEST_FILES = \
test_switch.xhtml \
switch-helper.svg \
test_text.html \
test_text_2.html \
test_text_scaled.html \
test_text_selection.html \
text-helper.svg \

View File

@ -0,0 +1,64 @@
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=655877
-->
<head>
<meta charset=UTF-8>
<title>Test for Bug 655877</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=655877">Mozilla Bug 655877</a>
<p id="display"></p>
<div id="content" style="display: none">
<svg width="400" height="200">
<text x="100" y="100" style="font: 16px sans-serif">
abc אבג 123 דהו defg
</text>
</svg>
</div>
<pre id="test">
<script class="testbody" type="application/javascript">
SimpleTest.waitForExplicitFinish();
function close(x, y, message) {
ok(Math.abs(x - y) < 1e-4, message);
}
function runTest() {
SpecialPowers.pushPrefEnv({'set': [['svg.text.css-frames.enabled', true]]}, function() {
document.getElementById("content").style.display = "block";
var text = document.querySelector("text");
// there are only 20 addressable characters
is(text.getNumberOfChars(), 20, "getNumberOfChars");
var sum, total = text.getComputedTextLength();
close(text.getSubStringLength(0, 20), total, "getSubStringLength all");
// add the advance of each glyph
sum = 0;
for (var i = 0; i < 20; i++) {
sum += text.getSubStringLength(i, 1);
}
close(total, sum, "sum getSubStringLength 1");
// split the text up into three chunks and add them together
close(total, text.getSubStringLength(0, 6) +
text.getSubStringLength(6, 8) +
text.getSubStringLength(14, 6), "sum getSubStringLength 2");
SimpleTest.finish();
});
}
window.addEventListener("load", runTest, false);
</script>
</pre>
</body>
</html>

View File

@ -1921,10 +1921,12 @@ TextRenderedRunIterator::Next()
mFrameIterator.DominantBaseline());
// Trim the offset/length to remove any leading/trailing white space.
uint32_t untrimmedOffset = offset;
uint32_t untrimmedLength = length;
nsTextFrame::TrimmedOffsets trimmedOffsets =
frame->GetTrimmedOffsets(frame->GetContent()->GetText(), true);
TrimOffsets(offset, length, trimmedOffsets);
charIndex += offset - untrimmedOffset;
// Determine if we should skip this rendered run.
bool skip = !mFrameIterator.IsWithinSubtree() ||
@ -1993,10 +1995,11 @@ public:
// Iterate over all original characters from the DOM that are within valid
// text content elements.
eOriginal,
// Iterate only over characters that are not skipped per the
// gfxSkipCharsIterator used for the text runs.
eNonSkipped,
// terate only over characters that are the first of clusters or ligature
// Iterate only over characters that are addressable by the positioning
// attributes x="", y="", etc. This includes all characters after
// collapsing white space as required by the value of 'white-space'.
eAddressable,
// Iterate only over characters that are the first of clusters or ligature
// groups.
eClusterAndLigatureGroupStart,
// Iterate only over characters that are part of a cluster or ligature
@ -2010,8 +2013,12 @@ public:
* @param aSVGTextFrame The nsSVGTextFrame2 whose characters to iterate
* through.
* @param aFilter Indicates which characters to iterate over.
* @param aSubtree A content subtree to track whether the current character
* is within.
*/
CharIterator(nsSVGTextFrame2* aSVGTextFrame, CharacterFilter aFilter);
CharIterator(nsSVGTextFrame2* aSVGTextFrame,
CharacterFilter aFilter,
nsIContent* aSubtree = nullptr);
/**
* Returns whether the iterator is finished.
@ -2022,11 +2029,23 @@ public:
}
/**
* Advances to the next character. Returns true if there was a character to
* advance to, and false otherwise.
* Advances to the next matching character. Returns true if there was a
* character to advance to, and false otherwise.
*/
bool Next();
/**
* Advances ahead aCount matching characters. Returns true if there were
* enough characters to advance past, and false otherwise.
*/
bool Next(uint32_t aCount);
/**
* Advances ahead up to aCount matching characters, stopping early if we move
* past the subtree (if one was specified in the constructor).
*/
void NextWithinSubtree(uint32_t aCount);
/**
* Advances to the character with the specified index. The index is in the
* space of original characters (i.e., all DOM characters under the <text>
@ -2045,6 +2064,13 @@ public:
*/
bool AdvancePastCurrentTextPathFrame();
/**
* Advances to the first matching character of the subtree. Returns true
* if we successfully advance to the subtree, or if we are already within
* the subtree. Returns false if we are past the subtree.
*/
bool AdvanceToSubtree();
/**
* Returns the nsTextFrame for the current character.
*/
@ -2053,6 +2079,22 @@ public:
return mFrameIterator.Current();
}
/**
* Returns whether the iterator is within the subtree.
*/
bool IsWithinSubtree() const
{
return mFrameIterator.IsWithinSubtree();
}
/**
* Returns whether the iterator is past the subtree.
*/
bool IsAfterSubtree() const
{
return mFrameIterator.IsAfterSubtree();
}
/**
* Returns whether the current character is a skipped character.
*/
@ -2209,9 +2251,10 @@ private:
};
CharIterator::CharIterator(nsSVGTextFrame2* aSVGTextFrame,
CharIterator::CharacterFilter aFilter)
CharIterator::CharacterFilter aFilter,
nsIContent* aSubtree)
: mFilter(aFilter),
mFrameIterator(aSVGTextFrame),
mFrameIterator(aSVGTextFrame, aSubtree),
mFrameForTrimCheck(nullptr),
mTrimmedOffset(0),
mTrimmedLength(0),
@ -2239,6 +2282,29 @@ CharIterator::Next()
return false;
}
bool
CharIterator::Next(uint32_t aCount)
{
while (aCount) {
if (!Next()) {
return false;
}
aCount--;
}
return true;
}
void
CharIterator::NextWithinSubtree(uint32_t aCount)
{
while (IsWithinSubtree() && aCount) {
if (!Next()) {
break;
}
aCount--;
}
}
bool
CharIterator::AdvanceToCharacter(uint32_t aTextElementCharIndex)
{
@ -2278,6 +2344,20 @@ CharIterator::AdvancePastCurrentTextPathFrame()
return true;
}
bool
CharIterator::AdvanceToSubtree()
{
while (!IsWithinSubtree()) {
if (IsAfterSubtree()) {
return false;
}
if (!AdvancePastCurrentFrame()) {
return false;
}
}
return true;
}
bool
CharIterator::IsClusterAndLigatureGroupStart() const
{
@ -2435,8 +2515,8 @@ CharIterator::MatchesFilter() const
return false;
}
if (mFilter == eNonSkipped) {
return true;
if (mFilter == eAddressable) {
return !IsOriginalCharUnaddressable();
}
return (mFilter == eClusterAndLigatureGroupStart) ==
@ -3490,28 +3570,23 @@ GetTextContentLength(nsIContent* aContent)
return length;
}
/**
* Returns the number of DOM characters beneath a node but which occur
* before a second node.
*
* @param aContent The node under which to look for nsTextNodes.
* @param aBefore The node before which, in document order, the candidate
* nsTextNodes are to be found.
*/
static uint32_t
GetTextContentLengthBefore(nsIContent* aContent, nsIContent* aBefore)
int32_t
nsSVGTextFrame2::ConvertTextElementCharIndexToAddressableIndex(
int32_t aIndex,
nsIContent* aContent)
{
NS_ASSERTION(aContent, "expected non-null aContent");
NS_ASSERTION(aBefore, "expected non-null aBefore");
uint32_t length = 0;
TextNodeIterator it(aContent, aBefore);
for (nsTextNode* text = it.Current();
text && !it.IsWithinSubtree() && !it.IsAfterSubtree();
text = it.Next()) {
length += text->TextLength();
CharIterator it(this, CharIterator::eAddressable, aContent);
if (!it.AdvanceToSubtree()) {
return -1;
}
return length;
uint32_t result = 0;
while (!it.AtEnd() &&
it.IsWithinSubtree() &&
it.TextElementCharIndex() < static_cast<uint32_t>(aIndex)) {
result++;
it.Next();
}
return result;
}
/**
@ -3521,7 +3596,15 @@ GetTextContentLengthBefore(nsIContent* aContent, nsIContent* aBefore)
uint32_t
nsSVGTextFrame2::GetNumberOfChars(nsIContent* aContent)
{
return GetTextContentLength(aContent);
uint32_t n = 0;
CharIterator it(this, CharIterator::eAddressable, aContent);
if (it.AdvanceToSubtree()) {
while (!it.AtEnd() && it.IsWithinSubtree()) {
n++;
it.Next();
}
}
return n;
}
/**
@ -3567,12 +3650,22 @@ nsSVGTextFrame2::GetSubStringLength(nsIContent* aContent,
return 0.0f;
}
// Convert charnum to be an offset into the whole <text> element, not just
// into aContent which might be a child <tspan>, etc.
charnum += GetTextContentLengthBefore(mContent, aContent);
// Convert charnum/nchars from addressable characters relative to
// aContent to global character indices.
CharIterator chit(this, CharIterator::eAddressable, aContent);
if (!chit.AdvanceToSubtree() ||
!chit.Next(charnum) ||
chit.IsAfterSubtree()) {
return 0.0f;
}
charnum = chit.TextElementCharIndex();
chit.NextWithinSubtree(nchars);
nchars = chit.TextElementCharIndex() - charnum;
// Find each rendered run that intersects with the range defined
// by charnum/nchars.
nscoord textLength = 0;
TextRenderedRunIterator it(this);
TextRenderedRunIterator it(this, TextRenderedRunIterator::eAllFrames);
TextRenderedRun run = it.Current();
while (run.mFrame) {
// If this rendered run is past the substring we are interested in, we
@ -3588,8 +3681,8 @@ nsSVGTextFrame2::GetSubStringLength(nsIContent* aContent,
IntersectInterval(offset, length, charnum, nchars);
if (length != 0) {
// Convert offset into an index into the run.
offset -= run.mTextElementCharIndex;
// Convert offset into an index into the frame.
offset += run.mTextFrameContentOffset - run.mTextElementCharIndex;
gfxSkipCharsIterator it =
run.mFrame->EnsureTextRun(nsTextFrame::eInflated);
@ -3637,7 +3730,7 @@ nsSVGTextFrame2::GetCharNumAtPosition(nsIContent* aContent,
// Hit test this rendered run. Later runs will override earlier ones.
int32_t index = run.GetCharNumAtPosition(context, p);
if (index != -1) {
result = index + run.mTextElementCharIndex;
result = index + run.mTextElementCharIndex - run.mTextFrameContentOffset;
}
}
@ -3645,8 +3738,7 @@ nsSVGTextFrame2::GetCharNumAtPosition(nsIContent* aContent,
return result;
}
// Transform the result index into an index relative to aContent.
return result - GetTextContentLengthBefore(mContent, aContent);
return ConvertTextElementCharIndexToAddressableIndex(result, aContent);
}
/**
@ -3660,18 +3752,12 @@ nsSVGTextFrame2::GetStartPositionOfChar(nsIContent* aContent,
{
UpdateGlyphPositioning(false);
uint32_t textBefore = GetTextContentLengthBefore(mContent, aContent);
uint32_t textWithin = GetTextContentLength(aContent);
if (aCharNum >= textWithin) {
CharIterator it(this, CharIterator::eAddressable, aContent);
if (!it.AdvanceToSubtree() ||
!it.Next(aCharNum)) {
return NS_ERROR_DOM_INDEX_SIZE_ERR;
}
CharIterator it(this, CharIterator::eNonSkipped);
if (!it.AdvanceToCharacter(aCharNum + textBefore)) {
return NS_ERROR_FAILURE;
}
// We need to return the start position of the whole glyph.
uint32_t startIndex = it.GlyphStartTextElementCharIndex();
@ -3690,18 +3776,12 @@ nsSVGTextFrame2::GetEndPositionOfChar(nsIContent* aContent,
{
UpdateGlyphPositioning(false);
uint32_t textBefore = GetTextContentLengthBefore(mContent, aContent);
uint32_t textWithin = GetTextContentLength(aContent);
if (aCharNum >= textWithin) {
CharIterator it(this, CharIterator::eAddressable, aContent);
if (!it.AdvanceToSubtree() ||
!it.Next(aCharNum)) {
return NS_ERROR_DOM_INDEX_SIZE_ERR;
}
CharIterator it(this, CharIterator::eNonSkipped);
if (!it.AdvanceToCharacter(aCharNum + textBefore)) {
return NS_ERROR_FAILURE;
}
// We need to return the end position of the whole glyph.
uint32_t startIndex = it.GlyphStartTextElementCharIndex();
@ -3733,18 +3813,12 @@ nsSVGTextFrame2::GetExtentOfChar(nsIContent* aContent,
{
UpdateGlyphPositioning(false);
uint32_t textBefore = GetTextContentLengthBefore(mContent, aContent);
uint32_t textWithin = GetTextContentLength(aContent);
if (aCharNum >= textWithin) {
CharIterator it(this, CharIterator::eAddressable, aContent);
if (!it.AdvanceToSubtree() ||
!it.Next(aCharNum)) {
return NS_ERROR_DOM_INDEX_SIZE_ERR;
}
CharIterator it(this, CharIterator::eNonSkipped);
if (!it.AdvanceToCharacter(aCharNum + textBefore)) {
return NS_ERROR_FAILURE;
}
nsPresContext* presContext = PresContext();
float cssPxPerDevPx = presContext->
@ -3790,13 +3864,13 @@ nsSVGTextFrame2::GetRotationOfChar(nsIContent* aContent,
{
UpdateGlyphPositioning(false);
uint32_t textBefore = GetTextContentLengthBefore(mContent, aContent);
uint32_t textWithin = GetTextContentLength(aContent);
if (aCharNum >= textWithin) {
CharIterator it(this, CharIterator::eAddressable, aContent);
if (!it.AdvanceToSubtree() ||
!it.Next(aCharNum)) {
return NS_ERROR_DOM_INDEX_SIZE_ERR;
}
*aResult = mPositions[aCharNum + textBefore].mAngle * 180.0 / M_PI;
*aResult = mPositions[it.TextElementCharIndex()].mAngle * 180.0 / M_PI;
return NS_OK;
}

View File

@ -395,6 +395,22 @@ private:
*/
void DoGlyphPositioning();
/**
* Converts the specified index into mPositions to an addressable
* character index (as can be used with the SVG DOM text methods)
* relative to the specified text child content element.
*
* @param aIndex The global character index.
* @param aContent The descendant text child content element that
* the returned addressable index will be relative to; null
* means the same as the <text> element.
* @return The addressable index, or -1 if the index cannot be
* represented as an addressable index relative to aContent.
*/
int32_t
ConvertTextElementCharIndexToAddressableIndex(int32_t aIndex,
nsIContent* aContent);
/**
* Recursive helper for ResolvePositions below.
*