+2011-04-20 Xiaomei Ji <xji@chromium.org>
+
+ Reviewed by Ryosuke Niwa.
+
+ Continue (3rd) experiment with moving caret by word in visual order.
+ https://bugs.webkit.org/show_bug.cgi?id=58294
+
+ * editing/selection/move-by-word-visually-expected.txt:
+ * editing/selection/move-by-word-visually.html:
+
2011-04-20 Cris Neckar <cdn@chromium.org>
Reviewed by Dirk Schulze.
======== Move By Word ====
Test 1, LTR:
Move right by one word
-"abc def hij opq rst"[0, 8, 12, 16, 16, 19] FAIL expected: [4, 8, 12, 16, 19, 19]
+"abc def hij opq rst"[0, 4, 8, 12, 16]
Move left by one word
-"abc def hij opq rst"[16, 12, 8, 4, 0, 0]
-Test 2, RTL:
+"abc def hij opq rst"[19, 16, 12, 8, 4, 0]
+Test 2, LTR:
+Move right by one word
+"abc def hij opq rst"[0, 4, 8, 12, 16]
+Move left by one word
+"abc def hij opq rst"[19, 16, 12, 8, 4, 0]
+Test 3, RTL:
+Move left by one word
+"abc def hij opq rst"[0, 15, 11, 7, 3]
+Move right by one word
+"abc def hij opq rst"[19, 3, 7, 11, 15, 0]
+Test 4, LTR:
+Move right by one word
+"ZZZ QQQ BBB CCC XXX"[0, 15, 11, 7, 3]
+Move left by one word
+"ZZZ QQQ BBB CCC XXX"[19, 3, 7, 11, 15, 0]
+Test 5, RTL:
+Move left by one word
+"ZZZ QQQ BBB CCC XXX"[0, 4, 8, 12, 16]
+Move right by one word
+"ZZZ QQQ BBB CCC XXX"[19, 16, 12, 8, 4, 0]
+Test 6, LTR:
+Move right by one word
+"abc def hij AAA BBB WWW opr uvw xyz"[0, 4, 8, 12, 19, 15, 24, 28, 32]
+Move left by one word
+"abc def hij AAA BBB WWW opr uvw xyz"[35, 32, 28, 24, 15, 19, 12, 8, 4, 0]
+Test 7, RTL:
+Move left by one word
+"abc def hij AAA BBB WWW opr uvw xyz"[0, 7, 3, 12, 16, 20, 24, 31, 27]
+Move right by one word
+"abc def hij AAA BBB WWW opr uvw xyz"[35, 27, 31, 24, 20, 16, 12, 3, 7, 0]
+Test 8, LTR:
+Move right by one word
+"abc def ZQB RIG uvw xyz"[0, 4, 8, 11, 16, 20]
+Move left by one word
+"abc def ZQB RIG uvw xyz"[23, 20, 16, 11, 8, 4, 0]
+Test 9, RTL:
+Move left by one word
+"abc def ZQB RIG uvw xyz"[0, 3, 8, 12, 16, 19]
+Move right by one word
+"abc def ZQB RIG uvw xyz"[23, 19, 16, 12, 8, 3, 0]
+Test 10, LTR:
+Move right by one word
+"aaa AAA bbb"[0, 4, 8]
+Move left by one word
+"aaa AAA bbb"[11, 8, 4, 0]
+Test 11, RTL:
+Move left by one word
+"aaa AAA bbb"[0, 4, 8, 11]
+Move right by one word
+"aaa AAA bbb"[11, 8, 4, 0]
+Test 12, LTR:
+Move right by one word
+"AAA BBB WWW aaa bbb ccc DDD SSS UUU"[0, 7, 3, 12, 16, 20, 24, 31, 27]
+Move left by one word
+"AAA BBB WWW aaa bbb ccc DDD SSS UUU"[35, 27, 31, 24, 20, 16, 12, 3, 7, 0]
+Test 13, RTL:
+Move left by one word
+"AAA BBB WWW aaa bbb ccc DDD SSS UUU"[0, 4, 8, 12, 19, 15, 24, 28, 32]
+Move right by one word
+"AAA BBB WWW aaa bbb ccc DDD SSS UUU"[35, 32, 28, 24, 15, 19, 12, 8, 4, 0]
+Test 14, LTR:
+Move right by one word
+"AAA BBB aaa bbb WWW DDD"[0, 3, 8, 12, 16, 19]
+Move left by one word
+"AAA BBB aaa bbb WWW DDD"[23, 19, 16, 12, 8, 3, 0]
+Test 15, RTL:
+Move left by one word
+"AAA BBB aaa bbb WWW DDD"[0, 4, 8, 11, 16, 20]
+Move right by one word
+"AAA BBB aaa bbb WWW DDD"[23, 20, 16, 11, 8, 4, 0]
+Test 16, LTR:
+Move right by one word
+"ZQB abc RIG"[0, 4, 8, 11]
Move left by one word
-"abc def hij opq rst"[0, 11, 7, 3, 3, 19] FAIL expected: [15, 11, 7, 3, 19, 19]
+"ZQB abc RIG"[11, 8, 4, 0]
+Test 17, RTL:
+Move left by one word
+"ZQB abc RIG"[0, 4, 8]
+Move right by one word
+"ZQB abc RIG"[11, 8, 4, 0]
+Test 18, LTR:
Move right by one word
-"abc def hij opq rst"[19, 7, 11, 15, 0, 0] FAIL expected: [3, 7, 11, 15, 0, 0]
-Test 3, LTR:
+"abc def hij opq"[0, 4, 15] FAIL expected: [0, 4, 11, 15]
+Move left by one word
+"abc def hij opq"[18, 15, 4, 0] FAIL expected: [18, 15, 11, 4, 0]
+Test 19, LTR:
Move right by one word
-"ZZZ QQQ BBB CCC XXX"[0, 11, 7, 3, 3, 19] FAIL expected: [15, 11, 7, 3, 19, 19]
+"AAA "[0, 3]
Move left by one word
-"ZZZ QQQ BBB CCC XXX"[19, 7, 11, 15, 0, 0] FAIL expected: [3, 7, 11, 15, 0, 0]
-Test 4, RTL:
+"BB"[2], "AAA "[3, 0]
+Test 20, RTL:
Move left by one word
-"ZZZ QQQ BBB CCC XXX"[0, 8, 12, 16, 16, 19] FAIL expected: [4, 8, 12, 16, 19, 19]
+"AAA "[0, 4]
Move right by one word
-"ZZZ QQQ BBB CCC XXX"[16, 12, 8, 4, 0, 0]
-Test 5, LTR:
+"BB"[2], "AAA "[4, 0]
+Test 21, LTR:
Move right by one word
-"abc def ZQB RIG uvw xyz"[0, 4, 8, 11, 20, 20, 23] FAIL expected: [4, 8, 11, 16, 20, 23, 23]
+"abc def "[0, 4, 8], "hij opq"[4], " rst uvw"[1, 5]
+Move left by one word
+" rst uvw"[8, 5, 1], "hij opq"[4], "abc def "[8, 4, 0]
+Test 22, RTL:
Move left by one word
-"abc def ZQB RIG uvw xyz"[20, 16, 4, 8, 4, 0, 0] FAIL expected: [20, 16, 11, 8, 4, 0, 0]
-Test 6, RTL:
+"abc def "[0], " rst uvw"[4], "hij opq"[3], "abc def "[7, 3] FAIL expected: ["abc def "[ 0, ]" rst uvw"[ 4, 0, ]"hij opq"[ 3, ]"abc def "[ 7, 3]
+Move right by one word
+" rst uvw"[8], "abc def "[3, 7], "hij opq"[3], " rst uvw"[4], "abc def "[0] FAIL expected: [" rst uvw"[ 8, ]"abc def "[ 3, 7, ]"hij opq"[ 3, ]" rst uvw"[ 0, 4, ]"abc def "[ 0]
+Test 23, RTL:
Move left by one word
-"abc def ZQB RIG uvw xyz"[0, 3, 12, 12, 16, 19, 23] FAIL expected: [3, 8, 12, 16, 19, 23, 23]
+"abc def "[0], " rst uvw"[4], "hij opq"[3], "abc def "[7, 3] FAIL expected: ["abc def "[ 0, ]" rst uvw"[ 4, 0, ]"hij opq"[ 3, ]"abc def "[ 7, 3]
Move right by one word
-"abc def ZQB RIG uvw xyz"[12, 16, 12, 8, 8, 0, 0] FAIL expected: [19, 16, 12, 8, 3, 0, 0]
-Test 7, LTR:
+" rst uvw"[8], "abc def "[3, 7], "hij opq"[3], " rst uvw"[4], "abc def "[0] FAIL expected: [" rst uvw"[ 8, ]"abc def "[ 3, 7, ]"hij opq"[ 3, ]" rst uvw"[ 0, 4, ]"abc def "[ 0]
+Test 24, LTR:
Move right by one word
-"ZQB abc RIG"[0, 4, 8, 11] FAIL expected: [4, 8, 11, 11]
+"abc def "[0, 4, 8], "hij opq"[4], " rst uvw"[1, 5]
Move left by one word
-"ZQB abc RIG"[4, 4, 4, 0] FAIL expected: [8, 4, 0, 0]
-Test 8, RTL:
+" rst uvw"[8, 5, 1], "hij opq"[4], "abc def "[8, 4, 0]
+Test 25, RTL:
Move left by one word
-"ZQB abc RIG"[0, 4, 8, 11] FAIL expected: [4, 8, 11, 11]
+"ABD DSU "[0, 4, 8], "EJH FUX"[4], "FFZ LIG"[4]
+Move right by one word
+"FFZ LIG"[7, 4], "EJH FUX"[4], "ABD DSU "[8, 4, 0]
+Test 26, LTR:
Move right by one word
-"ZQB abc RIG"[8, 0, 0, 0] FAIL expected: [8, 4, 0, 0]
+"ABD DSU "[0], "FFZ LIG"[3], "EJH FUX"[3], "ABD DSU "[7, 3]
+Move left by one word
+"FFZ LIG"[7], "ABD DSU "[3, 7], "EJH FUX"[3], "FFZ LIG"[3], "ABD DSU "[0]
+Test 27, RTL:
+Move left by one word
+"ABD DSU "[0, 4, 8], "EJH FUX"[4], "FFZ LIG"[4]
+Move right by one word
+"FFZ LIG"[7, 4], "EJH FUX"[4], "ABD DSU "[8, 4, 0]
+Test 28, LTR:
+Move right by one word
+"ABD DSU "[0], "FFZ LIG"[3], "EJH FUX"[3], "ABD DSU "[7, 3]
+Move left by one word
+"FFZ LIG"[7], "ABD DSU "[3, 7], "EJH FUX"[3], "FFZ LIG"[3], "ABD DSU "[0]
+Test 29, RTL:
+Move left by one word
+"ABD DSU "[0, 4, 8], "abc def"[3], "FFZ LIG"[4]
+Move right by one word
+"FFZ LIG"[7, 4], "abc def"[3], "ABD DSU "[8, 4, 0]
+Test 30, LTR:
+Move right by one word
+"ABD DSU "[0], "FFZ LIG"[3], "ABD DSU "[8], "abc def"[4], "ABD DSU "[7, 3]
+Move left by one word
+"FFZ LIG"[7], "ABD DSU "[3, 7], "abc def"[4], "ABD DSU "[8], "FFZ LIG"[3] FAIL expected: ["FFZ LIG"[ 7, ]"ABD DSU "[ 3, 7, ]"abc def"[ 4, ]"ABD DSU "[ 8, ]"FFZ LIG"[ 3, ]"ABD DSU "[ 0]
+Test 31, RTL:
+Move left by one word
+"ABD DSU "[0, 4, 8], "abc def"[3], "FFZ LIG"[4]
+Move right by one word
+"FFZ LIG"[7, 4], "abc def"[3], "ABD DSU "[8, 4, 0]
+Test 32, LTR:
+Move right by one word
+"ABD DSU "[0, 3, 8], "abc def"[4], "FFZ LIG"[3]
+Move left by one word
+"FFZ LIG"[7, 3], "abc def"[4], "ABD DSU "[8, 4, 0]
+Test 33, RTL:
+Move left by one word
+"ABD opq DSU "[0, 4, 8], "abc AAA def"[8, 4, 3], "FFZ rst LIG"[4, 8] FAIL expected: ["ABD opq DSU "[ 0, 4, 8, 12, ]"abc AAA def"[ 4, 3, ]"FFZ rst LIG"[ 4, 8]
+Move right by one word
+"FFZ rst LIG"[11, 8, 4], "abc AAA def"[4, 8], "ABD opq DSU "[8, 4, 0] FAIL expected: ["FFZ rst LIG"[ 11, 8, 4, ]"abc AAA def"[ 3, 4, ]"ABD opq DSU "[ 12, 8, 4, 0]
+Test 34, LTR:
+Move right by one word
+"ABD opq DSU "[0, 4], "abc AAA def"[8, 4], "ABD opq DSU "[12, 11], "FFZ rst LIG"[4, 8, 11] FAIL expected: ["ABD opq DSU "[ 0, 4, 8, ]"abc AAA def"[ 8, 7, ]"ABD opq DSU "[ 12, 11, ]"FFZ rst LIG"[ 4, 8, 11]
+Move left by one word
+"FFZ rst LIG"[11, 8, 4], "ABD opq DSU "[11, 12], "abc AAA def"[7, 8], "ABD opq DSU "[7, 4, 0] FAIL expected: ["FFZ rst LIG"[ 11, 8, 4, ]"ABD opq DSU "[ 11, 12, ]"abc AAA def"[ 7, 8, ]"ABD opq DSU "[ 8, 4, 0]
+Test 35, RTL:
+Move left by one word
+"ABD opq DSU "[0, 4, 8], "abc AAA def"[4, 8], "FFZ rst LIG"[4, 8] FAIL expected: ["ABD opq DSU "[ 0, 4, 8, 12, ]"abc AAA def"[ 4, 8, ]"FFZ rst LIG"[ 4, 8]
+Move right by one word
+"FFZ rst LIG"[11, 8, 4], "abc AAA def"[8, 4], "ABD opq DSU "[11, 8, 4, 0] FAIL expected: ["FFZ rst LIG"[ 11, 8, 4, ]"abc AAA def"[ 8, 4, ]"ABD opq DSU "[ 12, 8, 4, 0]
+Test 36, LTR:
+Move right by one word
+"ABD opq DSU "[0, 4, 8, 12], "abc AAA def"[4, 8], "FFZ rst LIG"[4, 8, 11]
+Move left by one word
+"FFZ rst LIG"[11, 8, 4], "abc AAA def"[8, 4], "ABD opq DSU "[12, 8, 4, 0]
+Test 37, LTR:
+Move right by one word
+"aaa "[0, 4], "bbb AAA "[4, 7]
+Move left by one word
+"FFZ"[3], "bbb AAA "[7, 4], "aaa "[4, 0]
======== Move By Word Specific Test ====
Test 1
document.getElementById("console").appendChild(document.createTextNode(messages.join("")));
}
-function caretCoordinates()
-{
- if (!window.textInputController)
- return { x: 0, y :0 };
- var caretRect = textInputController.firstRectForCharacterRange(textInputController.selectedRange()[0], 0);
- return { x: caretRect[0], y: caretRect[1] };
-}
-
-
function fold(string)
{
var result = "";
log("]");
}
+function nodeOfWordBreak(nodeAndOffset)
+{
+ var node = document.getElementById(nodeAndOffset[0]).firstChild;
+ if (nodeAndOffset.length == 3) {
+ var childIndex = nodeAndOffset[2];
+ for (var i = 0; i < childIndex - 1; ++i) {
+ node = node.nextSibling;
+ }
+ }
+ return node;
+}
+
var wordBreaks;
+function logWordBreak(index)
+{
+ var wordBreak = wordBreaks[index];
+ if (wordBreak.search(',') == -1)
+ log(wordBreak);
+ else {
+ var nodeAndOffset = wordBreak.split(',');
+ var node = nodeOfWordBreak(nodeAndOffset);
+
+ var differentNode = false;
+ if (index != 0) {
+ differentNode = nodeOfWordBreak(nodeAndOffset) != nodeOfWordBreak(wordBreaks[index - 1].split(','));
+
+ }
+ if (differentNode == true)
+ log("]");
+ if (index == 0 || differentNode == true)
+ log((node instanceof Text ? '"' + fold(node.data) + '"' : "<" + node.tagName + ">") + "[");
+ log(nodeAndOffset[1]);
+ }
+}
+
+function positionEqualToWordBreak(position, wordBreak)
+{
+ if (wordBreak.search(',') == -1)
+ return position.offset == wordBreak;
+ else {
+ var nodeAndOffset = wordBreak.split(',');
+ return position.node == nodeOfWordBreak(nodeAndOffset) && position.offset == nodeAndOffset[1];
+ }
+}
+
function validateData(positions)
{
+ var equal = true;
+ if (positions.length != wordBreaks.length)
+ equal = false;
for (var i = 0; i < wordBreaks.length - 1; ++i) {
- if (positions[i].offset != wordBreaks[i + 1]) {
+ if (!positionEqualToWordBreak(positions[i], wordBreaks[i])) {
+ equal = false;
break;
}
}
- if (i != wordBreaks.length - 1 && positions[i] != wordBreaks[i]) {
+ if (equal == false) {
log(" FAIL expected: [");
- for (var i = 1; i < wordBreaks.length; ++i) {
- log(wordBreaks[i] + ", ");
+ for (var i = 0; i < wordBreaks.length; ++i) {
+ logWordBreak(i);
+ if (i != wordBreaks.length - 1)
+ log(", ");
}
- log(wordBreaks[wordBreaks.length - 1] + "]");
+ log("]");
}
}
function collectWordBreaks(test, searchDirection)
{
- if (searchDirection == "right") {
- if (test.getAttribute("dir") == 'ltr')
- wordBreaks = test.title.split("|")[0].split(" ");
- else
- wordBreaks = test.title.split("|")[1].split(" ");
- } else {
- if (test.getAttribute("dir") == 'ltr')
- wordBreaks = test.title.split("|")[1].split(" ");
- else
- wordBreaks = test.title.split("|")[0].split(" ");
+ var title;
+ if (searchDirection == "right")
+ title = test.title.split("|")[0];
+ else
+ title = test.title.split("|")[1];
+
+ var pattern = /\[(.+?)\]/g;
+ var result;
+ wordBreaks = [];
+ while ((result = pattern.exec(title)) != null) {
+ wordBreaks.push(result[1]);
+ }
+ if (wordBreaks.length == 0) {
+ wordBreaks = title.split(" ");
+ }
+}
+
+function setPosition(sel, node, wordBreak)
+{
+ if (wordBreak.search(',') == -1)
+ sel.setPosition(node, wordBreak);
+ else {
+ var nodeAndOffset = wordBreak.split(',');
+ sel.setPosition(nodeOfWordBreak(nodeAndOffset), nodeAndOffset[1]);
}
}
{
log("Move " + searchDirection + " by one word\n");
var prevOffset = sel.anchorOffset;
- var node = sel.anchorNode;
- collectWordBreaks(test, searchDirection);
- sel.setPosition(node, wordBreaks[0]);
+ var prevNode = sel.anchorNode;
var positions = [];
- for (var index = 1; index < wordBreaks.length; ++index) {
+ positions.push({ node: sel.anchorNode, offset: sel.anchorOffset });
+
+ while (1) {
sel.modify("move", searchDirection, "-webkit-visual-word");
- positions.push({ node: sel.anchorNode, offset: sel.anchorOffset, point: caretCoordinates() });
- sel.setPosition(node, wordBreaks[index]);
- }
- sel.modify("move", searchDirection, "-webkit-visual-word");
- positions.push({ node: sel.anchorNode, offset: sel.anchorOffset, point: caretCoordinates() });
+ if (prevNode == sel.anchorNode && prevOffset == sel.anchorOffset)
+ break;
+ positions.push({ node: sel.anchorNode, offset: sel.anchorOffset });
+ prevNode = sel.anchorNode;
+ prevOffset = sel.anchorOffset;
+ };
logPositions(positions);
+ collectWordBreaks(test, searchDirection);
validateData(positions);
log("\n");
}
if (dir == "rtl")
direction = "left";
moveByWord(sel, test, direction, dir);
+
+ sel.modify("move", "forward", "lineBoundary");
// Check ctrl-left-arrow works for every position.
if (dir == "ltr")
direction = "left";
var positions = [];
sel.setPosition(tests[i].firstChild, startOffset);
- positions.push({ node: sel.anchorNode, offset: sel.anchorOffset, point: caretCoordinates() });
+ positions.push({ node: sel.anchorNode, offset: sel.anchorOffset });
sel.modify("move", movingDirection, "-webkit-visual-word");
- positions.push({ node: sel.anchorNode, offset: sel.anchorOffset, point: caretCoordinates() });
+ positions.push({ node: sel.anchorNode, offset: sel.anchorOffset });
logPositions(positions);
</head>
<body>
<div id="testMoveByWord">
-<!-- The numbers put in title are starting word boundaries.
-The numbers printed out in the output are ending word boundaries. -->
-<div dir=ltr class="test_move_by_word" title="0 4 8 12 16 19|19 16 12 8 4 0" contenteditable>abc def hij opq rst</div>
-<div dir=rtl class="test_move_by_word" title="0 15 11 7 3 19|19 3 7 11 15 0" contenteditable>abc def hij opq rst</div>
-<div dir=ltr class="test_move_by_word" title="0 15 11 7 3 19|19 3 7 11 15 0" contenteditable>ששש נננ בבב גגג קקק</div>
-<div dir=rtl class="test_move_by_word" title="0 4 8 12 16 19|19 16 12 8 4 0" contenteditable>ששש נננ בבב גגג קקק</div>
-<div dir=ltr class="test_move_by_word" title="0 4 8 11 16 20 23|23 20 16 11 8 4 0" contenteditable>abc def שנב סטז uvw xyz</div>
-<div dir=rtl class="test_move_by_word" title="0 3 8 12 16 19 23|23 19 16 12 8 3 0" contenteditable>abc def שנב סטז uvw xyz</div>
+<!--
+Title saves the word breaks.
+The format of title is "xxx|xxxx".
+
+The sequence on the left of "|" is word boundaries when moving caret from left to right.
+The sequence on the right of "|" is word boundaries when moving caret from right to left.
+
+If there is a single node in the line, the sequence are offsets.
+If there are multiple nodes in the line, the sequence is array of [anchor_node_id, offset, child_node_index],
+where child_node_index is optional, default is the first child of the anchor node.
+-->
+<div dir=ltr class="test_move_by_word" title="0 4 8 12 16|19 16 12 8 4 0" contenteditable>abc def hij opq rst</div>
+
+<!-- pure English -->
+<div dir=ltr class="test_move_by_word" title="0 4 8 12 16|19 16 12 8 4 0" contenteditable>abc def hij opq rst</div>
+<div dir=rtl class="test_move_by_word" title="19 3 7 11 15 0|0 15 11 7 3" contenteditable>abc def hij opq rst</div>
+
+<!-- pure Hebrew -->
+<div dir=ltr class="test_move_by_word" title="0 15 11 7 3|19 3 7 11 15 0" contenteditable>ששש נננ בבב גגג קקק</div>
+<div dir=rtl class="test_move_by_word" title="19 16 12 8 4 0|0 4 8 12 16" contenteditable>ששש נננ בבב גגג קקק</div>
+
+<!-- bidi text -->
+<!-- English Hebrew English -->
+<div dir=ltr class="test_move_by_word" title="0 4 8 12 19 15 24 28 32|35 32 28 24 15 19 12 8 4 0" contenteditable>abc def hij אאא בבב צצצ opr uvw xyz</div>
+<div dir=rtl class="test_move_by_word" title="35 27 31 24 20 16 12 3 7 0|0 7 3 12 16 20 24 31 27" contenteditable>abc def hij אאא בבב צצצ opr uvw xyz</div>
+
+<div dir=ltr class="test_move_by_word" title="0 4 8 11 16 20|23 20 16 11 8 4 0" contenteditable>abc def שנב סטז uvw xyz</div>
+<div dir=rtl class="test_move_by_word" title="23 19 16 12 8 3 0|0 3 8 12 16 19" contenteditable>abc def שנב סטז uvw xyz</div>
+
+<div dir=ltr class="test_move_by_word" title="0 4 8|11 8 4 0" contenteditable>aaa אאא bbb</div>
+<div dir=rtl class="test_move_by_word" title="11 8 4 0|0 4 8 11" contenteditable>aaa אאא bbb</div>
+
+<!-- Hebrew English Hebrew -->
+<div dir=ltr class="test_move_by_word" title="0 7 3 12 16 20 24 31 27|35 27 31 24 20 16 12 3 7 0" contenteditable>אאא בבב צצצ aaa bbb ccc דדד עעע פפפ</div>
+<div dir=rtl class="test_move_by_word" title="35 32 28 24 15 19 12 8 4 0|0 4 8 12 19 15 24 28 32" contenteditable>אאא בבב צצצ aaa bbb ccc דדד עעע פפפ</div>
+
+<div dir=ltr class="test_move_by_word" title="0 3 8 12 16 19|23 19 16 12 8 3 0" contenteditable>אאא בבב aaa bbb צצצ דדד</div>
+<div dir=rtl class="test_move_by_word" title="23 20 16 11 8 4 0|0 4 8 11 16 20" contenteditable>אאא בבב aaa bbb צצצ דדד</div>
+
<div dir=ltr class="test_move_by_word" title="0 4 8 11|11 8 4 0" contenteditable>שנב abc סטז</div>
-<div dir=rtl class="test_move_by_word" title="0 4 8 11|11 8 4 0" contenteditable>שנב abc סטז</div>
+<div dir=rtl class="test_move_by_word" title="11 8 4 0|0 4 8" contenteditable>שנב abc סטז</div>
+
+<!-- multiple spaces between word.
+ FAILED: word break between "def" and "hij" is unreachable.
+-->
+<div dir=ltr class="test_move_by_word" title="0 4 11 15|18 15 11 4 0" contenteditable>abc def hij opq</div>
+
+<!-- Inline element -->
+<div dir=ltr id="div_1" class="test_move_by_word" title="[div_1, 0][div_1, 3]|[span_1, 2][div_1, 3][div_1,0]" contenteditable>אאא <span id="span_1">בב</span></div>
+<div dir=rtl id="div_2" class="test_move_by_word" title="[span_2, 2][div_2, 4][div_2, 0]|[div_2, 0][div_2, 4]" contenteditable>אאא <span id="span_2">בב</span></div>
+
+<!-- pure English in inline element with same or different directionality from its parent -->
+<div dir=ltr id="div_3" class="test_move_by_word" title="[div_3, 0][div_3, 4][div_3, 8][span_3, 4][div_3, 1, 3][div_3, 5, 3]|[div_3, 8, 3][div_3, 5, 3][div_3, 1, 3][span_3, 4][div_3, 8][div_3, 4][div_3, 0]" contenteditable>abc def <span id="span_3">hij opq</span> rst uvw</div>
+
+<!-- FAILED -->
+<div dir=rtl id="div_4" class="test_move_by_word" title="[div_4, 8, 3][div_4, 3, 1][div_4, 7, 1][span_4, 3, 1][div_4, 0, 3][div_4, 4, 3][div_4, 0, 1]|[div_4, 0, 1][div_4, 4, 3][div_4, 0, 3][span_4, 3, 1][div_4, 7, 1][div_4, 3, 1]" contenteditable>abc def <span id="span_4">hij opq</span> rst uvw</div>
+
+<!-- FAILED -->
+<div id="div_5" dir=rtl class="test_move_by_word" title="[div_5, 8, 3][div_5, 3, 1][div_5, 7, 1][span_5, 3, 1][div_5, 0, 3][div_5, 4, 3][div_5, 0, 1]|[div_5, 0, 1][div_5, 4, 3][div_5, 0, 3][span_5, 3, 1][div_5, 7, 1][div_5, 3, 1]"contenteditable>abc def <span dir=ltr id="span_5">hij opq</span> rst uvw</div>
+
+<div id="div_6" dir=ltr class="test_move_by_word" title="[div_6, 0, 1][div_6, 4, 1][div_6, 8, 1][span_6, 4, 1][div_6, 1, 3][div_6, 5, 3]|[div_6, 8, 3][div_6, 5, 3][div_6, 1, 3][span_6, 4, 1][div_6, 8, 1][div_6, 4, 1][div_6, 0, 1]" contenteditable>abc def <span dir=rtl id="span_6">hij opq</span> rst uvw</div>
+
+<!-- pure Hebrew in inline element with same or different directionality from its parent -->
+
+<div id="div_7" dir=rtl class="test_move_by_word" title="[div_7, 7, 3][div_7, 4, 3][span_7, 4, 1][div_7, 8, 1][div_7, 4, 1][div_7, 0, 1]|[div_7, 0, 1][div_7, 4, 1][div_7, 8, 1][span_7, 4, 1][div_7, 4, 3]" contenteditable>אבד דעפ <span dir=ltr id="span_7">היח ופק</span>ווש כטז</div>
+
+<div id="div_8" dir=ltr class="test_move_by_word" title="[div_8, 0, 1][div_8, 3, 3][span_8, 3, 1][div_8, 7, 1][div_8, 3, 1]|[div_8, 7, 3][div_8, 3, 1][div_8, 7, 1][span_8, 3, 1][div_8, 3, 3][div_8, 0, 1]" contenteditable>אבד דעפ <span dir=rtl id="span_8">היח ופק</span>ווש כטז</div>
+
+<div id="div_9" dir=rtl class="test_move_by_word" title="[div_9, 7, 3][div_9, 4, 3][span_9, 4, 1][div_9, 8, 1][div_9, 4, 1][div_9, 0, 1]|[div_9, 0, 1][div_9, 4, 1][div_9, 8, 1][span_9, 4, 1][div_9, 4, 3]" contenteditable>אבד דעפ <span id="span_9">היח ופק</span>ווש כטז</div>
+
+<div id="div_10" dir=ltr class="test_move_by_word" title="[div_10, 0, 1][div_10, 3, 3][span_10, 3, 1][div_10, 7, 1][div_10, 3, 1]|[div_10, 7, 3][div_10, 3, 1][div_10, 7, 1][span_10, 3, 1][div_10, 3, 3][div_10, 0, 1]" contenteditable>אבד דעפ <span id="span_10">היח ופק</span>ווש כטז</div>
+
+<!-- bidi in inline element with same or different directionality from its parent -->
+<div id="div_11" dir=rtl class="test_move_by_word" title="[div_11, 7, 3][div_11, 4, 3][span_11, 3, 1][div_11, 8, 1][div_11, 4, 1][div_11, 0, 1]|[div_11, 0, 1][div_11, 4, 1][div_11, 8, 1][span_11, 3, 1][div_11, 4, 3]" contenteditable>אבד דעפ <span dir=ltr id="span_11">abc def</span>ווש כטז</div>
+
+<!-- FAIL -->
+<div id="div_12" dir=ltr class="test_move_by_word" title="[div_12, 0, 1][div_12, 3, 3][div_12, 8, 1][span_12, 4, 1][div_12, 7, 1][div_12, 3, 1]|[div_12, 7, 3][div_12, 3, 1][div_12, 7, 1][span_12, 4, 1][div_12, 8, 1][div_12, 3, 3][div_12, 0]" contenteditable>אבד דעפ <span dir=rtl id="span_12">abc def</span>ווש כטז</div>
+
+<div id="div_13" dir=rtl class="test_move_by_word" title="[div_13, 7, 3][div_13, 4, 3][span_13, 3, 1][div_13, 8, 1][div_13, 4, 1][div_13, 0, 1]|[div_13, 0, 1][div_13, 4, 1][div_13, 8, 1][span_13, 3, 1][div_13, 4, 3]" contenteditable>אבד דעפ <span id="span_13">abc def</span>ווש כטז</div>
+
+<div id="div_14" dir=ltr class="test_move_by_word" title="[div_14, 0, 1][div_14, 3, 1][div_14, 8, 1][span_14, 4, 1][div_14, 3, 3]|[div_14, 7, 3][div_14, 3, 3][span_14, 4, 1][div_14, 8, 1][div_14, 4, 1][div_14, 0, 1]" contenteditable>אבד דעפ <span id="span_14">abc def</span>ווש כטז</div>
+
+<!-- FAILED -->
+<div id="div_15" dir=rtl class="test_move_by_word" title="[div_15, 11, 3][div_15, 8, 3][div_15, 4, 3][span_15, 3, 1][span_15, 4, 1][div_15, 12, 1][div_15, 8, 1][div_15, 4, 1][div_15, 0, 1]|[div_15, 0, 1][div_15, 4, 1][div_15, 8, 1][div_15, 12, 1][span_15, 4, 1][span_15, 3, 1][div_15, 4, 3][div_15, 8, 3]"
+contenteditable>אבד opq דעפ <span dir=ltr id="span_15">abc אאא def</span>ווש rst כטז</div>
+
+<!-- FAILED, and wrong printing result -->
+<div id="div_16" dir=ltr class="test_move_by_word" title="[div_16, 0, 1][div_16, 4, 1][div_16, 8, 1][span_16, 8, 1][span_16, 7, 1][div_16, 12, 1][div_16, 11, 1][div_16, 4, 3][div_16, 8, 3][div_16, 11, 3]|[div_16, 11, 3][div_16, 8, 3][div_16, 4, 3][div_16, 11, 1][div_16, 12, 1][span_16, 7, 1][span_16, 8, 1][div_16, 8, 1][div_16, 4, 1][div_16, 0, 1]" contenteditable>אבד opq דעפ <span dir=rtl id="span_16">abc אאא def</span>ווש rst כטז</div>
+
+<!-- FAILED -->
+<div id="div_17" dir=rtl class="test_move_by_word" title="[div_17, 11, 3][div_17, 8, 3][div_17, 4, 3][span_17, 8, 1][span_17, 4, 1][div_17, 12, 1][div_17, 8, 1][div_17, 4, 1][div_17, 0, 1]|[div_17, 0, 1][div_17, 4, 1][div_17, 8, 1][div_17, 12, 1][span_17, 4, 1][span_17, 8, 1][div_17, 4, 3][div_17, 8, 3]" contenteditable>אבד opq דעפ <span id="span_17">abc אאא def</span>ווש rst כטז</div>
+
+<div id="div_18" dir=ltr class="test_move_by_word" title="[div_18, 0, 1][div_18, 4, 1][div_18, 8, 1][div_18, 12, 1][span_18, 4, 1][span_18, 8, 1][div_18, 4, 3][div_18, 8, 3][div_18, 11, 3]|[div_18, 11, 3][div_18, 8, 3][div_18, 4, 3][span_18, 8, 1][span_18, 4, 1][div_18, 12, 1][div_18, 8, 1][div_18, 4, 1][div_18, 0, 1]" contenteditable>אבד opq דעפ <span id="span_18">abc אאא def</span>ווש rst כטז</div>
+
+<div id="div_19" dir=ltr class="test_move_by_word" title="[div_19, 0, 1][div_19, 4, 1][span_19, 4, 1][span_19, 7, 1]|[div_19, 3, 3][span_19, 7, 1][span_19, 4, 1][div_19, 4, 1][div_19, 0, 1]" contenteditable>aaa <span id="span_19">bbb אאא </span>ווש</div>
<!-- The content in title means "startOffsetInCurrentNode movingDirectionByWord endingNode endingOffsetInEndingNode" -->
-<div id="div_1" contenteditable>אאא <span id="span_1" class="specific_test" title="1 left div_1 0">בב</span></div>
+<div id="div_100" contenteditable>אאא <span id="span_100" class="specific_test" title="1 left div_100 0">בב</span></div>
</div>
<pre id="console"></pre>
+2011-04-20 Xiaomei Ji <xji@chromium.org>
+
+ Reviewed by Ryosuke Niwa.
+
+ Continue (3rd) experiment with moving caret by word in visual order.
+ https://bugs.webkit.org/show_bug.cgi?id=58294
+
+ This patch along with r82588 and r83483 implements moving caret by
+ word in visual order.
+
+ The overall algorithm is:
+ 1. First get the InlineBox and offset of the pass-in VisiblePosition.
+ 2. Based on the position (left boundary, middle, right boundary) of the offset and the
+ direction of the movement, look for visually adjacent word breaks.
+ 2.1 If the offset is the minimum offset of the box,
+ return the rightmost word boundary in previous boxes if moving left.
+ return the leftmost word boundary in box and next boxes if moving right.
+ 2.2 Similar for the case when offset is at the maximum offset of the box.
+ 2.3 When offset is inside the box (not at boundaries), first find the previousWordPosition
+ or nextWordPosition based on the directionality of the box. If this word break position
+ is also inside the same box, return it. Otherwise (the nextWordPosition or
+ previousWordPosition is not in the same box or is at the box boundary), collect all the
+ word breaks in the box and search for the one closest to the input "offset" based on
+ box directionality, block directionality, and movement direction. Continue search in
+ adjacent boxes if needed.
+
+ Notes:
+ 1. Word boundaries are collected one box at a time. Only when a boundary that is closest to
+ the input position (in the moving direction) is not available in current box, word
+ boundaries in adjacent box will be collected. So, there is no need to save InlineBox in
+ word boundaries. Instead, the word boundaries are saved as a pair
+ (VisiblePosition, offset) to avoid recomputing VisiblePosition.
+
+ 2. We only collect boundaries of the right kind (i.e. left boundary of a word in LTR block
+ and right boundary of a word in RTL block). And word boundaries are collected using
+ previousWordPosition() and nextWordPosition(). So when box directionality is the same as
+ block directionality, word boundaries are collected from right to left visually in a LTR
+ box, and word boundaries are collected from left to right visually in a RTL box. It is
+ the other way around when box directionality is different from block directionality.
+
+ 3. To find the right kinds of word boundaries, we must move back and forth between words
+ in some situations. For example, if we're moving to the right in a LTR box in LTR block,
+ we cannot simply return nextWordPosition() because it would return the right boundary
+ of a word. Instead, we return nextWordPosition()'s nextWordPosition()'s previousWordPosition().
+
+ 4. When collecting word breaks inside a box, it first computes a start position, then
+ collect the right kind of word breaks until it reaches the end of (or beyond) the box.
+ In the meanwhile, it might need special handling on the rightmost or leftmost position
+ based on the directionality of the box and block. These computations do not consider the
+ box's bidi level.
+
+ * editing/visible_units.cpp:
+ (WebCore::nextWordBreakInBoxInsideBlockWithDifferentDirectionality):
+ (WebCore::collectWordBreaksInBox):
+ (WebCore::previousWordBoundaryInBox):
+ (WebCore::nextWordBoundaryInBox):
+ (WebCore::visuallyLastWordBoundaryInBox):
+ (WebCore::leftWordBoundary):
+ (WebCore::rightWordBoundary):
+ (WebCore::leftWordPosition):
+ (WebCore::rightWordPosition):
+
2011-04-20 Cris Neckar <cdn@chromium.org>
Reviewed by Dirk Schulze.
bool hasSeenWordBreakInThisBox = previousWordBreak.isNotNull();
VisiblePosition wordBreak = hasSeenWordBreakInThisBox ? previousWordBreak : Position(box->renderer()->node(), box->caretMinOffset(), Position::PositionIsOffsetInAnchor);
wordBreak = nextWordPosition(wordBreak);
-
- if (wordBreak == previousWordBreak) {
- isLastWordBreakInBox = true;
- return VisiblePosition();
- }
-
-
+
// Given RTL box "ABC DEF" either follows a LTR box or is the first visual box in an LTR block as an example,
// the visual display of the RTL box is: "(0)J(10)I(9)H(8) (7)F(6)E(5)D(4) (3)C(2)B(1)A(11)",
// where the number in parenthesis represents offset in visiblePosition.
// Start at offset 0, the first word break is at offset 3, the 2nd word break is at offset 7, and the 3rd word break should be at offset 0.
// But nextWordPosition() of offset 7 is offset 11, which should be ignored,
// and the position at offset 0 should be manually added as the last word break within the box.
- if (positionIsVisuallyOrderedInBoxInBlockWithDifferentDirectionality(wordBreak, box, offsetOfWordBreak)) {
+ if (wordBreak != previousWordBreak && positionIsVisuallyOrderedInBoxInBlockWithDifferentDirectionality(wordBreak, box, offsetOfWordBreak)) {
isLastWordBreakInBox = false;
return wordBreak;
}
break;
}
}
+
+static void collectWordBreaksInBox(const InlineBox* box, WordBoundaryVector& orderedWordBoundaries, TextDirection blockDirection)
+{
+ if (box->direction() == blockDirection)
+ collectWordBreaksInBoxInsideBlockWithSameDirectionality(box, orderedWordBoundaries);
+ else
+ collectWordBreaksInBoxInsideBlockWithDifferntDirectionality(box, orderedWordBoundaries);
+}
-static VisiblePosition previousWordBreakInBox(const InlineBox* box, int offset, TextDirection blockDirection)
+static VisiblePosition previousWordBoundaryInBox(const InlineBox* box, int offset)
{
int offsetOfWordBreak = 0;
VisiblePosition wordBreak;
while (true) {
- if (box->direction() == blockDirection)
- wordBreak = previousWordBreakInBoxInsideBlockWithSameDirectionality(box, wordBreak, offsetOfWordBreak);
- // FIXME: Implement the 'else' case when the box direction is not equal to the block direction.
+ wordBreak = previousWordBreakInBoxInsideBlockWithSameDirectionality(box, wordBreak, offsetOfWordBreak);
if (wordBreak.isNull())
break;
if (offset == invalidOffset || offsetOfWordBreak != offset)
return VisiblePosition();
}
+static VisiblePosition nextWordBoundaryInBox(const InlineBox* box, int offset)
+{
+ int offsetOfWordBreak = 0;
+ VisiblePosition wordBreak;
+ bool isLastWordBreakInBox = false;
+ do {
+ wordBreak = nextWordBreakInBoxInsideBlockWithDifferentDirectionality(box, wordBreak, offsetOfWordBreak, isLastWordBreakInBox);
+ if (wordBreak.isNotNull() && (offset == invalidOffset || offsetOfWordBreak != offset))
+ return wordBreak;
+ } while (!isLastWordBreakInBox);
+ return VisiblePosition();
+}
+
+static VisiblePosition visuallyLastWordBoundaryInBox(const InlineBox* box, int offset, TextDirection blockDirection)
+{
+ WordBoundaryVector orderedWordBoundaries;
+ collectWordBreaksInBox(box, orderedWordBoundaries, blockDirection);
+ if (!orderedWordBoundaries.size())
+ return VisiblePosition();
+ if (offset == invalidOffset || orderedWordBoundaries[orderedWordBoundaries.size() - 1].offsetInInlineBox != offset)
+ return orderedWordBoundaries[orderedWordBoundaries.size() - 1].visiblePosition;
+ if (orderedWordBoundaries.size() > 1)
+ return orderedWordBoundaries[orderedWordBoundaries.size() - 2].visiblePosition;
+ return VisiblePosition();
+}
+
static int greatestValueUnder(int offset, bool boxAndBlockAreInSameDirection, const WordBoundaryVector& orderedWordBoundaries)
{
if (!orderedWordBoundaries.size())
}
return invalidOffset;
}
-
+
static VisiblePosition leftWordBoundary(const InlineBox* box, int offset, TextDirection blockDirection)
{
VisiblePosition wordBreak;
for (const InlineBox* adjacentBox = box; adjacentBox; adjacentBox = adjacentBox->prevLeafChild()) {
- if (blockDirection == LTR)
- wordBreak = previousWordBreakInBox(adjacentBox, adjacentBox == box ? offset : invalidOffset, blockDirection);
- // FIXME: Implement the "else" case.
+ if (blockDirection == LTR) {
+ if (box->isLeftToRightDirection())
+ wordBreak = previousWordBoundaryInBox(adjacentBox, adjacentBox == box ? offset : invalidOffset);
+ else
+ wordBreak = nextWordBoundaryInBox(adjacentBox, adjacentBox == box ? offset : invalidOffset);
+ } else
+ wordBreak = visuallyLastWordBoundaryInBox(adjacentBox, adjacentBox == box ? offset : invalidOffset, blockDirection);
if (wordBreak.isNotNull())
return wordBreak;
}
VisiblePosition wordBreak;
for (const InlineBox* adjacentBox = box; adjacentBox; adjacentBox = adjacentBox->nextLeafChild()) {
- if (blockDirection == RTL)
- wordBreak = previousWordBreakInBox(adjacentBox, adjacentBox == box ? offset : invalidOffset, blockDirection);
- // FIXME: Implement the "else" case.
+ if (blockDirection == RTL) {
+ if (box->isLeftToRightDirection())
+ wordBreak = nextWordBoundaryInBox(adjacentBox, adjacentBox == box ? offset : invalidOffset);
+ else
+ wordBreak = previousWordBoundaryInBox(adjacentBox, adjacentBox == box ? offset : invalidOffset);
+ } else
+ wordBreak = visuallyLastWordBoundaryInBox(adjacentBox, adjacentBox == box ? offset : invalidOffset, blockDirection);
if (!wordBreak.isNull())
return wordBreak;
}
return wordBreak;
WordBoundaryVector orderedWordBoundaries;
- if (box->direction() == blockDirection)
- collectWordBreaksInBoxInsideBlockWithSameDirectionality(box, orderedWordBoundaries);
- else
- collectWordBreaksInBoxInsideBlockWithDifferntDirectionality(box, orderedWordBoundaries);
+ collectWordBreaksInBox(box, orderedWordBoundaries, blockDirection);
int index = box->isLeftToRightDirection() ? greatestValueUnder(offset, blockDirection == LTR, orderedWordBoundaries) :
smallestOffsetAbove(offset, blockDirection == RTL, orderedWordBoundaries);
return wordBreak;
WordBoundaryVector orderedWordBoundaries;
- if (box->direction() == blockDirection)
- collectWordBreaksInBoxInsideBlockWithSameDirectionality(box, orderedWordBoundaries);
- else
- collectWordBreaksInBoxInsideBlockWithDifferntDirectionality(box, orderedWordBoundaries);
+ collectWordBreaksInBox(box, orderedWordBoundaries, blockDirection);
+
int index = box->isLeftToRightDirection() ? smallestOffsetAbove(offset, blockDirection == LTR, orderedWordBoundaries) :
greatestValueUnder(offset, blockDirection == RTL, orderedWordBoundaries);
if (index != invalidOffset)