var section = function(runEmitter, codes) { var s = { parts: per(runEmitter).map(function(p) { return part(p, codes); }).all(), ascent: 0, descent: 0, width: 0, length: 0, plainText: '' }; s.parts.forEach(function(p) { s.ascent = Math.max(s.ascent, p.ascent); s.descent = Math.max(s.descent, p.descent); s.width += p.width; // XXX: look again at this // if (!p.run.deleted) { s.length += runs.getPieceLength(p.run.text); // } s.plainText += runs.getPiecePlainText(p.run.text); }); return s; };
var checkInlines = function(expected, text) { assert(expected, per(characters(text)).per(split()).map(function(word) { return !word ? null : getPlainText(word.text, word.spaces) + getPlainText(word.spaces, word.end); }).all()) };
load: function(runs, takeFocus) { var self = this; this.undo = []; this.redo = []; this._wordOrdinals = []; this.words = per(characters(runs)).per(split(self.codes)).map(function(w) { //console.log('here\'s a word', w); return word(w, self.codes); }).all(); this.layout(); this.contentChanged.fire(); this.select(0, 0, takeFocus); },
spliceWordsWithRuns: function(wordIndex, count, runs) { var self = this; var newWords = per(characters(runs)) .per(split(self.codes)) .truthy() .map(function(w) { return word(w, self.codes); }) .all(); // Check if old or new content contains any fancy control codes: var runFilters = false; if ('_filtersRunning' in self) { self._filtersRunning++; } else { for (var n = 0; n < count; n++) { if (this.words[wordIndex + n].code()) { runFilters = true; } } if (!runFilters) { runFilters = newWords.some(function(word) { return !!word.code(); }); } } this.transaction(function(log) { makeEditCommand(self, wordIndex, count, newWords)(log); if (runFilters) { self._filtersRunning = 0; try { for (;;) { var spliceCount = self._filtersRunning; if (!self.editFilters.some(function(filter) { filter(self); return spliceCount !== self._filtersRunning; })) { break; // No further changes were made } } } finally { delete self._filtersRunning; } } }); },
layout: function() { this.frame = null; try { this.frame = per(this.words).per(frame(0, 0, this._width, 0, this)).first(); } catch (x) { console.error(x); } if (!this.frame) { console.error('A bug somewhere has produced an invalid state - rolling back'); this.performUndo(); } else if (this._nextSelection) { var next = this._nextSelection; delete this._nextSelection; this.select(next.start, next.end); } },
draw: function(ctx, x, y) { per(this.text.parts).concat(this.space.parts).forEach(function(part) { part.draw(ctx, x, y); x += part.width; }); },
splice: function(start, end, text, useDefaultFormatting) { // sample the surrounding run to get an idea of which formatting keys to apply to the new text we're inserting. var textType = typeof text === 'string'; if (textType) { var sample = Math.max(0, start - 1); var sampleRun = per({ start: sample, end: sample + 1 }) .per(this.runs, this) .first(); } if (textType && (undefined != sampleRun)) { text = [ sampleRun ? Object.create(sampleRun, { text: { value: text } }) : { text: text } ]; } else if (!Array.isArray(text)) { text = [{ text: text }]; } if (useDefaultFormatting) this.applyDefaultFormatting(text); else this.applyInsertFormatting(text); var startWord = this.wordContainingOrdinal(start), endWord = this.wordContainingOrdinal(end); var prefix; if (start === startWord.ordinal) { if (startWord.index > 0 && !isBreaker(this.words[startWord.index - 1])) { startWord.index--; var previousWord = this.words[startWord.index]; prefix = per({}).per(previousWord.runs, previousWord).all(); } else { prefix = []; } } else { prefix = per({ end: startWord.offset }) .per(startWord.word.runs, startWord.word) .all(); } var suffix; if (end === endWord.ordinal) { if ((end === this.frame.length - 1) || isBreaker(endWord.word)) { suffix = []; endWord.index--; } else { suffix = per({}).per(endWord.word.runs, endWord.word).all(); } } else { suffix = per({ start: endWord.offset }) .per(endWord.word.runs, endWord.word) .all(); } var oldLength = this.frame.length; this.spliceWordsWithRuns(startWord.index, (endWord.index - startWord.index) + 1, per(prefix).concat(text).concat(suffix).per(runs.consolidate()).all()); return this.frame ? (this.frame.length - oldLength) : 0; },
var testToRuns = function(expected, start, count) { var startChar = chars.skip(start).first(); var finishChar = chars.skip(start + count).first(); assert(expected, per(startChar.cut(finishChar)).map('x.text').all()); };
var getPlainText = function(from, to) { return per(from.cut(to)).map(function(run) { return runs.getPiecePlainText(run.text); }).all().join(''); };
expected = JSON.stringify(expected); actual = JSON.stringify(actual); if (expected != actual) { console.error(''); console.error('============= ERROR ============='); console.error(''); console.error('Expected: ' + expected); console.error(' Actual: ' + actual); console.error(''); failed++; } else { console.log(' Good: ' + actual); } }; var chars = per(characters(sampleText)); var plain = ' Crampton Wick,\n 26th Oct 2013\n\nDear sir/madam,\n\nWith reference to your account No. 17598732, ' + 'it is with the utmost regret that we have to inform you that your contract with us has been terminated ' + 'forthwith.\n\n Please find enclosed a portrait of Her Majesty Queen Victoria brushing a gibbon\'s ' + 'hair.\n\nYours, etc.\n\n'; var expectedChars = plain.split(''); expectedChars.splice(70, 0, {"thing":"blue"}); assert( expectedChars, chars.take(expectedChars.length).map('x.char').all() ); var testToRuns = function(expected, start, count) {