зеркало из https://github.com/mozilla/gecko-dev.git
Bug 842119. r=longsonr
This commit is contained in:
Родитель
e8568d35a6
Коммит
32b9560efc
|
@ -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 \
|
||||
|
|
|
@ -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>
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
*
|
||||
|
|
Загрузка…
Ссылка в новой задаче