adding all weblabels from weblabels.fsf.org
[weblabels.fsf.org.git] / etherpad.fsf.org / 20120516 / files / virtual_lines.js
CommitLineData
5a920362 1/**
2 * This code is mostly from the old Etherpad. Please help us to comment this code.
3 * This helps other people to understand this code better and helps them to improve it.
4 * TL;DR COMMENTS ON THIS FILE ARE HIGHLY APPRECIATED
5 */
6
7/**
8 * Copyright 2009 Google Inc.
9 *
10 * Licensed under the Apache License, Version 2.0 (the "License");
11 * you may not use this file except in compliance with the License.
12 * You may obtain a copy of the License at
13 *
14 * http://www.apache.org/licenses/LICENSE-2.0
15 *
16 * Unless required by applicable law or agreed to in writing, software
17 * distributed under the License is distributed on an "AS-IS" BASIS,
18 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
19 * See the License for the specific language governing permissions and
20 * limitations under the License.
21 */
22
23function makeVirtualLineView(lineNode)
24{
25
26 // how much to jump forward or backward at once in a charSeeker before
27 // constructing a DOM node and checking the coordinates (which takes a
28 // significant fraction of a millisecond). From the
29 // coordinates and the approximate line height we can estimate how
30 // many lines we have moved. We risk being off if the number of lines
31 // we move is on the order of the line height in pixels. Fortunately,
32 // when the user boosts the font-size they increase both.
33 var maxCharIncrement = 20;
34 var seekerAtEnd = null;
35
36 function getNumChars()
37 {
38 return lineNode.textContent.length;
39 }
40
41 function getNumVirtualLines()
42 {
43 if (!seekerAtEnd)
44 {
45 var seeker = makeCharSeeker();
46 seeker.forwardByWhile(maxCharIncrement);
47 seekerAtEnd = seeker;
48 }
49 return seekerAtEnd.getVirtualLine() + 1;
50 }
51
52 function getVLineAndOffsetForChar(lineChar)
53 {
54 var seeker = makeCharSeeker();
55 seeker.forwardByWhile(maxCharIncrement, null, lineChar);
56 var theLine = seeker.getVirtualLine();
57 seeker.backwardByWhile(8, function()
58 {
59 return seeker.getVirtualLine() == theLine;
60 });
61 seeker.forwardByWhile(1, function()
62 {
63 return seeker.getVirtualLine() != theLine;
64 });
65 var lineStartChar = seeker.getOffset();
66 return {
67 vline: theLine,
68 offset: (lineChar - lineStartChar)
69 };
70 }
71
72 function getCharForVLineAndOffset(vline, offset)
73 {
74 // returns revised vline and offset as well as absolute char index within line.
75 // if offset is beyond end of line, for example, will give new offset at end of line.
76 var seeker = makeCharSeeker();
77 // go to start of line
78 seeker.binarySearch(function()
79 {
80 return seeker.getVirtualLine() >= vline;
81 });
82 var lineStart = seeker.getOffset();
83 var theLine = seeker.getVirtualLine();
84 // go to offset, overshooting the virtual line only if offset is too large for it
85 seeker.forwardByWhile(maxCharIncrement, null, lineStart + offset);
86 // get back into line
87 seeker.backwardByWhile(1, function()
88 {
89 return seeker.getVirtualLine() != theLine;
90 }, lineStart);
91 var lineChar = seeker.getOffset();
92 var theOffset = lineChar - lineStart;
93 // handle case of last virtual line; should be able to be at end of it
94 if (theOffset < offset && theLine == (getNumVirtualLines() - 1))
95 {
96 var lineLen = getNumChars();
97 theOffset += lineLen - lineChar;
98 lineChar = lineLen;
99 }
100
101 return {
102 vline: theLine,
103 offset: theOffset,
104 lineChar: lineChar
105 };
106 }
107
108 return {
109 getNumVirtualLines: getNumVirtualLines,
110 getVLineAndOffsetForChar: getVLineAndOffsetForChar,
111 getCharForVLineAndOffset: getCharForVLineAndOffset,
112 makeCharSeeker: function()
113 {
114 return makeCharSeeker();
115 }
116 };
117
118 function deepFirstChildTextNode(nd)
119 {
120 nd = nd.firstChild;
121 while (nd && nd.firstChild) nd = nd.firstChild;
122 if (nd.data) return nd;
123 return null;
124 }
125
126 function makeCharSeeker( /*lineNode*/ )
127 {
128
129 function charCoords(tnode, i)
130 {
131 var container = tnode.parentNode;
132
133 // treat space specially; a space at the end of a virtual line
134 // will have weird coordinates
135 var isSpace = (tnode.nodeValue.charAt(i) === " ");
136 if (isSpace)
137 {
138 if (i == 0)
139 {
140 if (container.previousSibling && deepFirstChildTextNode(container.previousSibling))
141 {
142 tnode = deepFirstChildTextNode(container.previousSibling);
143 i = tnode.length - 1;
144 container = tnode.parentNode;
145 }
146 else
147 {
148 return {
149 top: container.offsetTop,
150 left: container.offsetLeft
151 };
152 }
153 }
154 else
155 {
156 i--; // use previous char
157 }
158 }
159
160
161 var charWrapper = document.createElement("SPAN");
162
163 // wrap the character
164 var tnodeText = tnode.nodeValue;
165 var frag = document.createDocumentFragment();
166 frag.appendChild(document.createTextNode(tnodeText.substring(0, i)));
167 charWrapper.appendChild(document.createTextNode(tnodeText.substr(i, 1)));
168 frag.appendChild(charWrapper);
169 frag.appendChild(document.createTextNode(tnodeText.substring(i + 1)));
170 container.replaceChild(frag, tnode);
171
172 var result = {
173 top: charWrapper.offsetTop,
174 left: charWrapper.offsetLeft + (isSpace ? charWrapper.offsetWidth : 0),
175 height: charWrapper.offsetHeight
176 };
177
178 while (container.firstChild) container.removeChild(container.firstChild);
179 container.appendChild(tnode);
180
181 return result;
182 }
183
184 var lineText = lineNode.textContent;
185 var lineLength = lineText.length;
186
187 var curNode = null;
188 var curChar = 0;
189 var curCharWithinNode = 0
190 var curTop;
191 var curLeft;
192 var approxLineHeight;
193 var whichLine = 0;
194
195 function nextNode()
196 {
197 var n = curNode;
198 if (!n) n = lineNode.firstChild;
199 else n = n.nextSibling;
200 while (n && !deepFirstChildTextNode(n))
201 {
202 n = n.nextSibling;
203 }
204 return n;
205 }
206
207 function prevNode()
208 {
209 var n = curNode;
210 if (!n) n = lineNode.lastChild;
211 else n = n.previousSibling;
212 while (n && !deepFirstChildTextNode(n))
213 {
214 n = n.previousSibling;
215 }
216 return n;
217 }
218
219 var seeker;
220 if (lineLength > 0)
221 {
222 curNode = nextNode();
223 var firstCharData = charCoords(deepFirstChildTextNode(curNode), 0);
224 approxLineHeight = firstCharData.height;
225 curTop = firstCharData.top;
226 curLeft = firstCharData.left;
227
228 function updateCharData(tnode, i)
229 {
230 var coords = charCoords(tnode, i);
231 whichLine += Math.round((coords.top - curTop) / approxLineHeight);
232 curTop = coords.top;
233 curLeft = coords.left;
234 }
235
236 seeker = {
237 forward: function(numChars)
238 {
239 var oldChar = curChar;
240 var newChar = curChar + numChars;
241 if (newChar > (lineLength - 1)) newChar = lineLength - 1;
242 while (curChar < newChar)
243 {
244 var curNodeLength = deepFirstChildTextNode(curNode).length;
245 var toGo = curNodeLength - curCharWithinNode;
246 if (curChar + toGo > newChar || !nextNode())
247 {
248 // going to next node would be too far
249 var n = newChar - curChar;
250 if (n >= toGo) n = toGo - 1;
251 curChar += n;
252 curCharWithinNode += n;
253 break;
254 }
255 else
256 {
257 // go to next node
258 curChar += toGo;
259 curCharWithinNode = 0;
260 curNode = nextNode();
261 }
262 }
263 updateCharData(deepFirstChildTextNode(curNode), curCharWithinNode);
264 return curChar - oldChar;
265 },
266 backward: function(numChars)
267 {
268 var oldChar = curChar;
269 var newChar = curChar - numChars;
270 if (newChar < 0) newChar = 0;
271 while (curChar > newChar)
272 {
273 if (curChar - curCharWithinNode <= newChar || !prevNode())
274 {
275 // going to prev node would be too far
276 var n = curChar - newChar;
277 if (n > curCharWithinNode) n = curCharWithinNode;
278 curChar -= n;
279 curCharWithinNode -= n;
280 break;
281 }
282 else
283 {
284 // go to prev node
285 curChar -= curCharWithinNode + 1;
286 curNode = prevNode();
287 curCharWithinNode = deepFirstChildTextNode(curNode).length - 1;
288 }
289 }
290 updateCharData(deepFirstChildTextNode(curNode), curCharWithinNode);
291 return oldChar - curChar;
292 },
293 getVirtualLine: function()
294 {
295 return whichLine;
296 },
297 getLeftCoord: function()
298 {
299 return curLeft;
300 }
301 };
302 }
303 else
304 {
305 curLeft = lineNode.offsetLeft;
306 seeker = {
307 forward: function(numChars)
308 {
309 return 0;
310 },
311 backward: function(numChars)
312 {
313 return 0;
314 },
315 getVirtualLine: function()
316 {
317 return 0;
318 },
319 getLeftCoord: function()
320 {
321 return curLeft;
322 }
323 };
324 }
325 seeker.getOffset = function()
326 {
327 return curChar;
328 };
329 seeker.getLineLength = function()
330 {
331 return lineLength;
332 };
333 seeker.toString = function()
334 {
335 return "seeker[curChar: " + curChar + "(" + lineText.charAt(curChar) + "), left: " + seeker.getLeftCoord() + ", vline: " + seeker.getVirtualLine() + "]";
336 };
337
338 function moveByWhile(isBackward, amount, optCondFunc, optCharLimit)
339 {
340 var charsMovedLast = null;
341 var hasCondFunc = ((typeof optCondFunc) == "function");
342 var condFunc = optCondFunc;
343 var hasCharLimit = ((typeof optCharLimit) == "number");
344 var charLimit = optCharLimit;
345 while (charsMovedLast !== 0 && ((!hasCondFunc) || condFunc()))
346 {
347 var toMove = amount;
348 if (hasCharLimit)
349 {
350 var untilLimit = (isBackward ? curChar - charLimit : charLimit - curChar);
351 if (untilLimit < toMove) toMove = untilLimit;
352 }
353 if (toMove < 0) break;
354 charsMovedLast = (isBackward ? seeker.backward(toMove) : seeker.forward(toMove));
355 }
356 }
357
358 seeker.forwardByWhile = function(amount, optCondFunc, optCharLimit)
359 {
360 moveByWhile(false, amount, optCondFunc, optCharLimit);
361 }
362 seeker.backwardByWhile = function(amount, optCondFunc, optCharLimit)
363 {
364 moveByWhile(true, amount, optCondFunc, optCharLimit);
365 }
366 seeker.binarySearch = function(condFunc)
367 {
368 // returns index of boundary between false chars and true chars;
369 // positions seeker at first true char, or else last char
370 var trueFunc = condFunc;
371 var falseFunc = function()
372 {
373 return !condFunc();
374 };
375 seeker.forwardByWhile(20, falseFunc);
376 seeker.backwardByWhile(20, trueFunc);
377 seeker.forwardByWhile(10, falseFunc);
378 seeker.backwardByWhile(5, trueFunc);
379 seeker.forwardByWhile(1, falseFunc);
380 return seeker.getOffset() + (condFunc() ? 0 : 1);
381 }
382
383 return seeker;
384 }
385
386}
387
388exports.makeVirtualLineView = makeVirtualLineView;