/** * Copyright 2009 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS-IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ function newSpanList() { function encodeInt(num) { num = num & 0x1fffffff; // neither 16-bit char can be 0x0001 or mistakable for the other return String.fromCharCode(((num >> 15) & 0x3fff) | 0x4000) + String.fromCharCode((num & 0x7fff) | 0x8000); } function decodeInt(str) { return ((str.charCodeAt(0) & 0x3fff) << 15) | (str.charCodeAt(1) & 0x7fff); } function indexOfInt(str, n) { var result = str.indexOf(encodeInt(n)); if (result < 0) return result; return result/2; } function intAt(str, i) { var idx = i*2; return decodeInt(str.substr(idx, 2)); } function numInts(str) { return str.length/2; } function subrange(str, start, end) { return str.substring(start*2, end*2); } var nil = "\1\1"; var repeatNilCache = ['']; function repeatNil(times) { while (times >= repeatNilCache.length) { repeatNilCache.push(repeatNilCache[repeatNilCache.length-1] + nil); } return repeatNilCache[times]; } function indexOfNonnil(str, start) { startIndex = (start || 0)*2; var nonnilChar = /[^\1]/g; nonnilChar.lastIndex = startIndex; var result = nonnilChar.exec(str); if (! result) { return str.length/2; } return (nonnilChar.lastIndex - 1)/2; } function intSplice(str, start, delCount, newStr) { return str.substring(0, start*2) + newStr + str.substring((start+delCount)*2); } function entryWidth(entry) { if ((typeof entry) == "number") return entry; return (entry && entry.width) || 1; } // "func" is a function over 0..(numItems-1) that is monotonically // "increasing" with index (false, then true). Finds the boundary // between false and true, a number between 0 and numItems inclusive. function binarySearch(numItems, func) { if (numItems < 1) return 0; if (func(0)) return 0; if (! func(numItems-1)) return numItems; var low = 0; // func(low) is always false var high = numItems-1; // func(high) is always true while ((high - low) > 1) { var x = Math.floor((low+high)/2); // x != low, x != high if (func(x)) high = x; else low = x; } return high; } var NEXT_ID = 1; var entryList = ""; // e.g. 2, 4, 3, 6 var charList = ""; // e.g. nil, nil, nil, 2, nil, 4, nil, nil, nil, nil, 3, 6 var keyToId = {}; var idToKey = {}; var idToEntry = {}; var idToNextId = {}; var idToPrevId = {}; var length = 0; var totalWidth = 0; function idAtIndex(i) { return intAt(entryList, i); } function indexOfId(id) { return indexOfInt(entryList, id); } function offsetOfId(id) { if (! id) return totalWidth; var entry = idToEntry[id]; var wid = entryWidth(entry); var lastCharLoc = indexOfInt(charList, id); return lastCharLoc + 1 - wid; } function idAtOffset(n) { return intAt(charList, indexOfNonnil(charList, n)); } var self = { length: function() { return length; }, totalWidth: function() { return totalWidth; }, next: function (entryOrKey) { if ((typeof entryOrKey) == "object") { var entry = entryOrKey; var id = idToNextId[keyToId[entry.key]]; if (id) return idToEntry[id]; return null; } else { var k = entryOrKey; var id = idToNextId[keyToId[k]]; if (id) return idToKey[id]; return null; } }, prev: function (entryOrKey) { if ((typeof entryOrKey) == "object") { var entry = entryOrKey; var id = idToPrevId[keyToId[entry.key]]; if (id) return idToEntry[id]; return null; } else { var k = entryOrKey; var id = idToPrevId[keyToId[k]]; if (id) return idToKey[id]; return null; } }, atKey: function (k) { return idToEntry[keyToId[k]]; }, atIndex: function (i) { return idToEntry[idAtIndex(i)]; }, keyAtIndex: function(i) { return idToKey[idAtIndex(i)]; }, keyAtOffset: function(n) { return idToKey[idAtOffset(n)]; }, containsKey: function (k) { return !! keyToId[k]; }, indexOfKey: function (k) { return indexOfId(keyToId[k]); }, indexOfEntry: function (entry) { return self.indexOfKey(entry.key); }, setKeyWidth: function (k, width) { var id = keyToId[k]; var charStart = offsetOfId(id); var oldWidth = entryWidth(idToEntry[id]); var toDelete = 0; var toInsert = 0; if (width < oldWidth) toDelete = oldWidth - width; else if (width > oldWidth) toInsert = width - oldWidth; charList = intSplice(charList, charStart, toDelete, repeatNil(toInsert)); totalWidth += (width - oldWidth); }, setEntryWidth: function (entry, width) { return self.setKeyWidth(entry.key, width); }, getEntryWidth: function (entry) { return entryWidth(entry); }, getKeyWidth: function (k) { return entryWidth(idToEntry[keyToId[k]]); }, offsetOfKey: function(k) { return offsetOfId(keyToId[k]); }, offsetOfEntry: function(entry) { return self.offsetOfKey(entry.key); }, offsetOfIndex: function (i) { if (i < 0) return 0; else if (i >= length) { return totalWidth; } else { return offsetOfId(idAtIndex(i)); } }, atOffset: function (n) { return idToEntry[idAtOffset(n)]; }, indexOfOffset: function (n) { if (n < 0) return 0; else if (n >= totalWidth) return length; return indexOfId(idAtOffset(n)); }, search: function(entryFunc) { return binarySearch(length, function (i) { return entryFunc(idToEntry[idAtIndex(i)]); }); }, push: function(entry, optKey) { self.splice(length, 0, [entry], (optKey && [optKey])); }, // entries can be objects with a 'key' property, possibly a 'width' property, // and any other properties; OR they can be just a width number, for an // ultra-light-weight representation. In the latter case the key array is // used to get the keys. Some functions become useless with this usage, i.e. // the ones that use an entry for identity. splice: function (start, deleteCount, newEntryArray, optKeyArray) { var charStart = self.offsetOfIndex(start); var charsToDelete = 0; var idBefore = ((start == 0) ? null : intAt(entryList, start-1)); // idAfter is mutated into id of node following deleted nodes var idAfter = ((start == length) ? null : (idBefore ? idToNextId[idBefore] : intAt(entryList, start))); if (deleteCount > 0) { var deleteId = idAfter; for(var i=0;i 0) { for(var i=0,n=newEntryArray.length; i 0) { // checkrep if (idToPrevId[idAtIndex(0)]) console.error("a"); if (idToNextId[idAtIndex(length-1)]) console.error("b"); for(var i=0;i