From b72546e088d5be0e6415e183b96a44bc8a7e7537 Mon Sep 17 00:00:00 2001 From: danielweck Date: Sun, 8 Feb 2015 23:26:43 +0000 Subject: [PATCH 01/22] initial checkin for EPUB3 Multiple Renditions (see readium-js commit log for details) --- lib/EpubReader.js | 29 +- lib/Readium.js | 59743 ++++++++++++++++++++++---------------------- readium-js | 2 +- 3 files changed, 29968 insertions(+), 29806 deletions(-) diff --git a/lib/EpubReader.js b/lib/EpubReader.js index 2c8f11056..e95f8f3a2 100644 --- a/lib/EpubReader.js +++ b/lib/EpubReader.js @@ -56,6 +56,27 @@ Readium){ // This function will retrieve a package document and load an EPUB var loadEbook = function (packageDocumentURL, readerSettings, openPageRequest) { + var renditionSelection = { + renditionLanguage: readerSettings.renditionSelectionLanguage, + renditionAccessMode: readerSettings.renditionSelectionAccessMode, + renditionLayout: readerSettings.renditionSelectionLayout, + renditionMediaQueryCallback: _.debounce(function() { + + console.debug("renditionMediaQueryCallback"); + + var data = { + embedded: embedded, + epub: url + }; + + console.debug("embedded: " + embedded); + console.debug("url: " + url); + + unloadReaderUI(); + applyKeyboardSettingsAndLoadUi(data); + }, 500, false) + }; + readium.openPackageDocument(packageDocumentURL, function(packageDocument, options){ currentDocument = packageDocument; currentDocument.generateTocListDOM(function(dom){ @@ -79,7 +100,7 @@ Readium){ $("#left-page-btn").on("click", prevPage); $("#right-page-btn").on("click", nextPage); - }, openPageRequest); + }, openPageRequest, renditionSelection); }; var spin = function() @@ -806,6 +827,12 @@ Readium){ if (settings.reader){ readerSettings = JSON.parse(settings.reader); } + +//TODO: UI to allow the user to configure these parameters +readerSettings.renditionSelectionLanguage = "en-UK"; +readerSettings.renditionSelectionAccessMode = "textual"; //auditory, tactile, visual +readerSettings.renditionSelectionLayout = "reflowable"; //pre-paginated + if (!embedded){ readerSettings = readerSettings || SettingsDialog.defaultSettings; SettingsDialog.updateReader(readium.reader, readerSettings); diff --git a/lib/Readium.js b/lib/Readium.js index 561475907..e0a70cbbf 100644 --- a/lib/Readium.js +++ b/lib/Readium.js @@ -1,582 +1,582 @@ +/** + * @license RequireJS text 2.0.6 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. + * Available via the MIT or new BSD license. + * see: http://github.com/requirejs/text for details + */ +/*jslint regexp: true */ +/*global require, XMLHttpRequest, ActiveXObject, + define, window, process, Packages, + java, location, Components, FileUtils */ + +define('text',['module'], function (module) { + + + var text, fs, Cc, Ci, + progIds = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'], + xmlRegExp = /^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im, + bodyRegExp = /]*>\s*([\s\S]+)\s*<\/body>/im, + hasLocation = typeof location !== 'undefined' && location.href, + defaultProtocol = hasLocation && location.protocol && location.protocol.replace(/\:/, ''), + defaultHostName = hasLocation && location.hostname, + defaultPort = hasLocation && (location.port || undefined), + buildMap = [], + masterConfig = (module.config && module.config()) || {}; + + text = { + version: '2.0.6', + + strip: function (content) { + //Strips declarations so that external SVG and XML + //documents can be added to a document without worry. Also, if the string + //is an HTML document, only the part inside the body tag is returned. + if (content) { + content = content.replace(xmlRegExp, ""); + var matches = content.match(bodyRegExp); + if (matches) { + content = matches[1]; + } + } else { + content = ""; + } + return content; + }, + + jsEscape: function (content) { + return content.replace(/(['\\])/g, '\\$1') + .replace(/[\f]/g, "\\f") + .replace(/[\b]/g, "\\b") + .replace(/[\n]/g, "\\n") + .replace(/[\t]/g, "\\t") + .replace(/[\r]/g, "\\r") + .replace(/[\u2028]/g, "\\u2028") + .replace(/[\u2029]/g, "\\u2029"); + }, + + createXhr: masterConfig.createXhr || function () { + //Would love to dump the ActiveX crap in here. Need IE 6 to die first. + var xhr, i, progId; + if (typeof XMLHttpRequest !== "undefined") { + return new XMLHttpRequest(); + } else if (typeof ActiveXObject !== "undefined") { + for (i = 0; i < 3; i += 1) { + progId = progIds[i]; + try { + xhr = new ActiveXObject(progId); + } catch (e) {} + + if (xhr) { + progIds = [progId]; // so faster next time + break; + } + } + } + + return xhr; + }, + + /** + * Parses a resource name into its component parts. Resource names + * look like: module/name.ext!strip, where the !strip part is + * optional. + * @param {String} name the resource name + * @returns {Object} with properties "moduleName", "ext" and "strip" + * where strip is a boolean. + */ + parseName: function (name) { + var modName, ext, temp, + strip = false, + index = name.indexOf("."), + isRelative = name.indexOf('./') === 0 || + name.indexOf('../') === 0; + + if (index !== -1 && (!isRelative || index > 1)) { + modName = name.substring(0, index); + ext = name.substring(index + 1, name.length); + } else { + modName = name; + } + + temp = ext || modName; + index = temp.indexOf("!"); + if (index !== -1) { + //Pull off the strip arg. + strip = temp.substring(index + 1) === "strip"; + temp = temp.substring(0, index); + if (ext) { + ext = temp; + } else { + modName = temp; + } + } + + return { + moduleName: modName, + ext: ext, + strip: strip + }; + }, + + xdRegExp: /^((\w+)\:)?\/\/([^\/\\]+)/, + + /** + * Is an URL on another domain. Only works for browser use, returns + * false in non-browser environments. Only used to know if an + * optimized .js version of a text resource should be loaded + * instead. + * @param {String} url + * @returns Boolean + */ + useXhr: function (url, protocol, hostname, port) { + var uProtocol, uHostName, uPort, + match = text.xdRegExp.exec(url); + if (!match) { + return true; + } + uProtocol = match[2]; + uHostName = match[3]; + + uHostName = uHostName.split(':'); + uPort = uHostName[1]; + uHostName = uHostName[0]; + + return (!uProtocol || uProtocol === protocol) && + (!uHostName || uHostName.toLowerCase() === hostname.toLowerCase()) && + ((!uPort && !uHostName) || uPort === port); + }, + + finishLoad: function (name, strip, content, onLoad) { + content = strip ? text.strip(content) : content; + if (masterConfig.isBuild) { + buildMap[name] = content; + } + onLoad(content); + }, + + load: function (name, req, onLoad, config) { + //Name has format: some.module.filext!strip + //The strip part is optional. + //if strip is present, then that means only get the string contents + //inside a body tag in an HTML string. For XML/SVG content it means + //removing the declarations so the content can be inserted + //into the current doc without problems. + + // Do not bother with the work if a build and text will + // not be inlined. + if (config.isBuild && !config.inlineText) { + onLoad(); + return; + } + + masterConfig.isBuild = config.isBuild; + + var parsed = text.parseName(name), + nonStripName = parsed.moduleName + + (parsed.ext ? '.' + parsed.ext : ''), + url = req.toUrl(nonStripName), + useXhr = (masterConfig.useXhr) || + text.useXhr; + + //Load the text. Use XHR if possible and in a browser. + if (!hasLocation || useXhr(url, defaultProtocol, defaultHostName, defaultPort)) { + text.get(url, function (content) { + text.finishLoad(name, parsed.strip, content, onLoad); + }, function (err) { + if (onLoad.error) { + onLoad.error(err); + } + }); + } else { + //Need to fetch the resource across domains. Assume + //the resource has been optimized into a JS module. Fetch + //by the module name + extension, but do not include the + //!strip part to avoid file system issues. + req([nonStripName], function (content) { + text.finishLoad(parsed.moduleName + '.' + parsed.ext, + parsed.strip, content, onLoad); + }); + } + }, + + write: function (pluginName, moduleName, write, config) { + if (buildMap.hasOwnProperty(moduleName)) { + var content = text.jsEscape(buildMap[moduleName]); + write.asModule(pluginName + "!" + moduleName, + "define(function () { return '" + + content + + "';});\n"); + } + }, + + writeFile: function (pluginName, moduleName, req, write, config) { + var parsed = text.parseName(moduleName), + extPart = parsed.ext ? '.' + parsed.ext : '', + nonStripName = parsed.moduleName + extPart, + //Use a '.js' file name so that it indicates it is a + //script that can be loaded across domains. + fileName = req.toUrl(parsed.moduleName + extPart) + '.js'; + + //Leverage own load() method to load plugin value, but only + //write out values that do not have the strip argument, + //to avoid any potential issues with ! in file names. + text.load(nonStripName, req, function (value) { + //Use own write() method to construct full module value. + //But need to create shell that translates writeFile's + //write() to the right interface. + var textWrite = function (contents) { + return write(fileName, contents); + }; + textWrite.asModule = function (moduleName, contents) { + return write.asModule(moduleName, fileName, contents); + }; + + text.write(pluginName, nonStripName, textWrite, config); + }, config); + } + }; + + if (masterConfig.env === 'node' || (!masterConfig.env && + typeof process !== "undefined" && + process.versions && + !!process.versions.node)) { + //Using special require.nodeRequire, something added by r.js. + fs = require.nodeRequire('fs'); + + text.get = function (url, callback) { + var file = fs.readFileSync(url, 'utf8'); + //Remove BOM (Byte Mark Order) from utf8 files if it is there. + if (file.indexOf('\uFEFF') === 0) { + file = file.substring(1); + } + callback(file); + }; + } else if (masterConfig.env === 'xhr' || (!masterConfig.env && + text.createXhr())) { + text.get = function (url, callback, errback, headers) { + var xhr = text.createXhr(), header; + xhr.open('GET', url, true); + + //Allow plugins direct access to xhr headers + if (headers) { + for (header in headers) { + if (headers.hasOwnProperty(header)) { + xhr.setRequestHeader(header.toLowerCase(), headers[header]); + } + } + } + + //Allow overrides specified in config + if (masterConfig.onXhr) { + masterConfig.onXhr(xhr, url); + } + + xhr.onreadystatechange = function (evt) { + var status, err; + //Do not explicitly handle errors, those should be + //visible via console output in the browser. + if (xhr.readyState === 4) { + status = xhr.status; + if (status > 399 && status < 600) { + //An http 4xx or 5xx error. Signal an error. + err = new Error(url + ' HTTP status: ' + status); + err.xhr = xhr; + errback(err); + } else { + callback(xhr.responseText); + } + + if (masterConfig.onXhrComplete) { + masterConfig.onXhrComplete(xhr, url); + } + } + }; + xhr.send(null); + }; + } else if (masterConfig.env === 'rhino' || (!masterConfig.env && + typeof Packages !== 'undefined' && typeof java !== 'undefined')) { + //Why Java, why is this so awkward? + text.get = function (url, callback) { + var stringBuffer, line, + encoding = "utf-8", + file = new java.io.File(url), + lineSeparator = java.lang.System.getProperty("line.separator"), + input = new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(file), encoding)), + content = ''; + try { + stringBuffer = new java.lang.StringBuffer(); + line = input.readLine(); + + // Byte Order Mark (BOM) - The Unicode Standard, version 3.0, page 324 + // http://www.unicode.org/faq/utf_bom.html + + // Note that when we use utf-8, the BOM should appear as "EF BB BF", but it doesn't due to this bug in the JDK: + // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4508058 + if (line && line.length() && line.charAt(0) === 0xfeff) { + // Eat the BOM, since we've already found the encoding on this file, + // and we plan to concatenating this buffer with others; the BOM should + // only appear at the top of a file. + line = line.substring(1); + } + + stringBuffer.append(line); + + while ((line = input.readLine()) !== null) { + stringBuffer.append(lineSeparator); + stringBuffer.append(line); + } + //Make sure we return a JavaScript string and not a Java string. + content = String(stringBuffer.toString()); //String + } finally { + input.close(); + } + callback(content); + }; + } else if (masterConfig.env === 'xpconnect' || (!masterConfig.env && + typeof Components !== 'undefined' && Components.classes && + Components.interfaces)) { + //Avert your gaze! + Cc = Components.classes, + Ci = Components.interfaces; + Components.utils['import']('resource://gre/modules/FileUtils.jsm'); + + text.get = function (url, callback) { + var inStream, convertStream, + readData = {}, + fileObj = new FileUtils.File(url); + + //XPCOM, you so crazy + try { + inStream = Cc['@mozilla.org/network/file-input-stream;1'] + .createInstance(Ci.nsIFileInputStream); + inStream.init(fileObj, 1, 0, false); + + convertStream = Cc['@mozilla.org/intl/converter-input-stream;1'] + .createInstance(Ci.nsIConverterInputStream); + convertStream.init(inStream, "utf-8", inStream.available(), + Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); + + convertStream.readString(inStream.available(), readData); + convertStream.close(); + inStream.close(); + callback(readData.value); + } catch (e) { + throw new Error((fileObj && fileObj.path || '') + ': ' + e); + } + }; + } + return text; +}); -/** - * @license RequireJS text 2.0.6 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. - * Available via the MIT or new BSD license. - * see: http://github.com/requirejs/text for details - */ -/*jslint regexp: true */ -/*global require, XMLHttpRequest, ActiveXObject, - define, window, process, Packages, - java, location, Components, FileUtils */ - -define('text',['module'], function (module) { - - - var text, fs, Cc, Ci, - progIds = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'], - xmlRegExp = /^\s*<\?xml(\s)+version=[\'\"](\d)*.(\d)*[\'\"](\s)*\?>/im, - bodyRegExp = /]*>\s*([\s\S]+)\s*<\/body>/im, - hasLocation = typeof location !== 'undefined' && location.href, - defaultProtocol = hasLocation && location.protocol && location.protocol.replace(/\:/, ''), - defaultHostName = hasLocation && location.hostname, - defaultPort = hasLocation && (location.port || undefined), - buildMap = [], - masterConfig = (module.config && module.config()) || {}; - - text = { - version: '2.0.6', - - strip: function (content) { - //Strips declarations so that external SVG and XML - //documents can be added to a document without worry. Also, if the string - //is an HTML document, only the part inside the body tag is returned. - if (content) { - content = content.replace(xmlRegExp, ""); - var matches = content.match(bodyRegExp); - if (matches) { - content = matches[1]; - } - } else { - content = ""; - } - return content; - }, - - jsEscape: function (content) { - return content.replace(/(['\\])/g, '\\$1') - .replace(/[\f]/g, "\\f") - .replace(/[\b]/g, "\\b") - .replace(/[\n]/g, "\\n") - .replace(/[\t]/g, "\\t") - .replace(/[\r]/g, "\\r") - .replace(/[\u2028]/g, "\\u2028") - .replace(/[\u2029]/g, "\\u2029"); - }, - - createXhr: masterConfig.createXhr || function () { - //Would love to dump the ActiveX crap in here. Need IE 6 to die first. - var xhr, i, progId; - if (typeof XMLHttpRequest !== "undefined") { - return new XMLHttpRequest(); - } else if (typeof ActiveXObject !== "undefined") { - for (i = 0; i < 3; i += 1) { - progId = progIds[i]; - try { - xhr = new ActiveXObject(progId); - } catch (e) {} - - if (xhr) { - progIds = [progId]; // so faster next time - break; - } - } - } - - return xhr; - }, - - /** - * Parses a resource name into its component parts. Resource names - * look like: module/name.ext!strip, where the !strip part is - * optional. - * @param {String} name the resource name - * @returns {Object} with properties "moduleName", "ext" and "strip" - * where strip is a boolean. - */ - parseName: function (name) { - var modName, ext, temp, - strip = false, - index = name.indexOf("."), - isRelative = name.indexOf('./') === 0 || - name.indexOf('../') === 0; - - if (index !== -1 && (!isRelative || index > 1)) { - modName = name.substring(0, index); - ext = name.substring(index + 1, name.length); - } else { - modName = name; - } - - temp = ext || modName; - index = temp.indexOf("!"); - if (index !== -1) { - //Pull off the strip arg. - strip = temp.substring(index + 1) === "strip"; - temp = temp.substring(0, index); - if (ext) { - ext = temp; - } else { - modName = temp; - } - } - - return { - moduleName: modName, - ext: ext, - strip: strip - }; - }, - - xdRegExp: /^((\w+)\:)?\/\/([^\/\\]+)/, - - /** - * Is an URL on another domain. Only works for browser use, returns - * false in non-browser environments. Only used to know if an - * optimized .js version of a text resource should be loaded - * instead. - * @param {String} url - * @returns Boolean - */ - useXhr: function (url, protocol, hostname, port) { - var uProtocol, uHostName, uPort, - match = text.xdRegExp.exec(url); - if (!match) { - return true; - } - uProtocol = match[2]; - uHostName = match[3]; - - uHostName = uHostName.split(':'); - uPort = uHostName[1]; - uHostName = uHostName[0]; - - return (!uProtocol || uProtocol === protocol) && - (!uHostName || uHostName.toLowerCase() === hostname.toLowerCase()) && - ((!uPort && !uHostName) || uPort === port); - }, - - finishLoad: function (name, strip, content, onLoad) { - content = strip ? text.strip(content) : content; - if (masterConfig.isBuild) { - buildMap[name] = content; - } - onLoad(content); - }, - - load: function (name, req, onLoad, config) { - //Name has format: some.module.filext!strip - //The strip part is optional. - //if strip is present, then that means only get the string contents - //inside a body tag in an HTML string. For XML/SVG content it means - //removing the declarations so the content can be inserted - //into the current doc without problems. - - // Do not bother with the work if a build and text will - // not be inlined. - if (config.isBuild && !config.inlineText) { - onLoad(); - return; - } - - masterConfig.isBuild = config.isBuild; - - var parsed = text.parseName(name), - nonStripName = parsed.moduleName + - (parsed.ext ? '.' + parsed.ext : ''), - url = req.toUrl(nonStripName), - useXhr = (masterConfig.useXhr) || - text.useXhr; - - //Load the text. Use XHR if possible and in a browser. - if (!hasLocation || useXhr(url, defaultProtocol, defaultHostName, defaultPort)) { - text.get(url, function (content) { - text.finishLoad(name, parsed.strip, content, onLoad); - }, function (err) { - if (onLoad.error) { - onLoad.error(err); - } - }); - } else { - //Need to fetch the resource across domains. Assume - //the resource has been optimized into a JS module. Fetch - //by the module name + extension, but do not include the - //!strip part to avoid file system issues. - req([nonStripName], function (content) { - text.finishLoad(parsed.moduleName + '.' + parsed.ext, - parsed.strip, content, onLoad); - }); - } - }, - - write: function (pluginName, moduleName, write, config) { - if (buildMap.hasOwnProperty(moduleName)) { - var content = text.jsEscape(buildMap[moduleName]); - write.asModule(pluginName + "!" + moduleName, - "define(function () { return '" + - content + - "';});\n"); - } - }, - - writeFile: function (pluginName, moduleName, req, write, config) { - var parsed = text.parseName(moduleName), - extPart = parsed.ext ? '.' + parsed.ext : '', - nonStripName = parsed.moduleName + extPart, - //Use a '.js' file name so that it indicates it is a - //script that can be loaded across domains. - fileName = req.toUrl(parsed.moduleName + extPart) + '.js'; - - //Leverage own load() method to load plugin value, but only - //write out values that do not have the strip argument, - //to avoid any potential issues with ! in file names. - text.load(nonStripName, req, function (value) { - //Use own write() method to construct full module value. - //But need to create shell that translates writeFile's - //write() to the right interface. - var textWrite = function (contents) { - return write(fileName, contents); - }; - textWrite.asModule = function (moduleName, contents) { - return write.asModule(moduleName, fileName, contents); - }; - - text.write(pluginName, nonStripName, textWrite, config); - }, config); - } - }; - - if (masterConfig.env === 'node' || (!masterConfig.env && - typeof process !== "undefined" && - process.versions && - !!process.versions.node)) { - //Using special require.nodeRequire, something added by r.js. - fs = require.nodeRequire('fs'); - - text.get = function (url, callback) { - var file = fs.readFileSync(url, 'utf8'); - //Remove BOM (Byte Mark Order) from utf8 files if it is there. - if (file.indexOf('\uFEFF') === 0) { - file = file.substring(1); - } - callback(file); - }; - } else if (masterConfig.env === 'xhr' || (!masterConfig.env && - text.createXhr())) { - text.get = function (url, callback, errback, headers) { - var xhr = text.createXhr(), header; - xhr.open('GET', url, true); - - //Allow plugins direct access to xhr headers - if (headers) { - for (header in headers) { - if (headers.hasOwnProperty(header)) { - xhr.setRequestHeader(header.toLowerCase(), headers[header]); - } - } - } - - //Allow overrides specified in config - if (masterConfig.onXhr) { - masterConfig.onXhr(xhr, url); - } - - xhr.onreadystatechange = function (evt) { - var status, err; - //Do not explicitly handle errors, those should be - //visible via console output in the browser. - if (xhr.readyState === 4) { - status = xhr.status; - if (status > 399 && status < 600) { - //An http 4xx or 5xx error. Signal an error. - err = new Error(url + ' HTTP status: ' + status); - err.xhr = xhr; - errback(err); - } else { - callback(xhr.responseText); - } - - if (masterConfig.onXhrComplete) { - masterConfig.onXhrComplete(xhr, url); - } - } - }; - xhr.send(null); - }; - } else if (masterConfig.env === 'rhino' || (!masterConfig.env && - typeof Packages !== 'undefined' && typeof java !== 'undefined')) { - //Why Java, why is this so awkward? - text.get = function (url, callback) { - var stringBuffer, line, - encoding = "utf-8", - file = new java.io.File(url), - lineSeparator = java.lang.System.getProperty("line.separator"), - input = new java.io.BufferedReader(new java.io.InputStreamReader(new java.io.FileInputStream(file), encoding)), - content = ''; - try { - stringBuffer = new java.lang.StringBuffer(); - line = input.readLine(); - - // Byte Order Mark (BOM) - The Unicode Standard, version 3.0, page 324 - // http://www.unicode.org/faq/utf_bom.html - - // Note that when we use utf-8, the BOM should appear as "EF BB BF", but it doesn't due to this bug in the JDK: - // http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4508058 - if (line && line.length() && line.charAt(0) === 0xfeff) { - // Eat the BOM, since we've already found the encoding on this file, - // and we plan to concatenating this buffer with others; the BOM should - // only appear at the top of a file. - line = line.substring(1); - } - - stringBuffer.append(line); - - while ((line = input.readLine()) !== null) { - stringBuffer.append(lineSeparator); - stringBuffer.append(line); - } - //Make sure we return a JavaScript string and not a Java string. - content = String(stringBuffer.toString()); //String - } finally { - input.close(); - } - callback(content); - }; - } else if (masterConfig.env === 'xpconnect' || (!masterConfig.env && - typeof Components !== 'undefined' && Components.classes && - Components.interfaces)) { - //Avert your gaze! - Cc = Components.classes, - Ci = Components.interfaces; - Components.utils['import']('resource://gre/modules/FileUtils.jsm'); - - text.get = function (url, callback) { - var inStream, convertStream, - readData = {}, - fileObj = new FileUtils.File(url); - - //XPCOM, you so crazy - try { - inStream = Cc['@mozilla.org/network/file-input-stream;1'] - .createInstance(Ci.nsIFileInputStream); - inStream.init(fileObj, 1, 0, false); - - convertStream = Cc['@mozilla.org/intl/converter-input-stream;1'] - .createInstance(Ci.nsIConverterInputStream); - convertStream.init(inStream, "utf-8", inStream.available(), - Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); - - convertStream.readString(inStream.available(), readData); - convertStream.close(); - inStream.close(); - callback(readData.value); - } catch (e) { - throw new Error((fileObj && fileObj.path || '') + ': ' + e); - } - }; - } - return text; -}); - -define('text!version.json',[],function () { return '{"readiumJs":{"sha":"0dc45978a2850e32bb2b3d5ff894d9ce1e306814","tag":"Release-0.10-395-g0dc4597","clean":true},"readiumSharedJs":{"sha":"1375f2de8ab214e352fb8ac863a71db150da84da","tag":"Release-0.10-474-g1375f2d","clean":true}}';}); - -/* -This code is required to IE for console shim -*/ -(function(){ - +define('text!version.json',[],function () { return '{"readiumJs":{"sha":"0dc45978a2850e32bb2b3d5ff894d9ce1e306814","tag":"Release-0.10-395-g0dc4597","clean":false},"readiumSharedJs":{"sha":"1375f2de8ab214e352fb8ac863a71db150da84da","tag":"Release-0.10-474-g1375f2d","clean":true}}';}); - if (!console["debug"]) console.debug = console.log; - if (!console["info"]) console.info = console.log; - if (!console["warn"]) console.warn = console.log; - if (!console["error"]) console.error = console.log; +/* +This code is required to IE for console shim +*/ + +(function(){ + + + if (!console["debug"]) console.debug = console.log; + if (!console["info"]) console.info = console.log; + if (!console["warn"]) console.warn = console.log; + if (!console["error"]) console.error = console.log; })(); define("console_shim", function(){}); -// LauncherOSX -// -// Created by Boris Schneiderman. -// Copyright (c) 2014 Readium Foundation and/or its licensees. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation and/or -// other materials provided with the distribution. -// 3. Neither the name of the organization nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED -// OF THE POSSIBILITY OF SUCH DAMAGE. - - -/** - * Top level ReadiumSDK namespace - * @namespace - */ -ReadiumSDK = { - - /** - * Current version of the JS SDK - * @static - * @return {string} version - */ - version: function () { - return "0.8.0"; - }, - /** - * @namespace - */ - Models: { - /** - * @type {object} - */ - Smil: {} - }, - /** - * @namespace - */ - Views: { - /** - * Landscape Orientation - */ - ORIENTATION_LANDSCAPE: "orientation_landscape", - /** - * Portrait Orientation - */ - ORIENTATION_PORTRAIT: "orientation_portrait" - }, - /** - * @namespace - */ - Collections: {}, - /** - * @namespace - */ - Routers: {}, - /** - * @namespace - */ - Helpers: {}, - /** - * @namespace - */ - Overrides: {}, - /** - * @namespace - */ - Events: { - /** - * @event - */ - READER_INITIALIZED: "ReaderInitialized", - /** - * This gets triggered on every page turnover. It includes spine information and such. - * @event - */ - PAGINATION_CHANGED: "PaginationChanged", - /** - * @event - */ - SETTINGS_APPLIED: "SettingsApplied", - /** - * @event - */ - FXL_VIEW_RESIZED: "FXLViewResized", - /** - * @event - */ - READER_VIEW_CREATED: "ReaderViewCreated", - /** - * @event - */ - READER_VIEW_DESTROYED: "ReaderViewDestroyed", - /** - * @event - */ - CONTENT_DOCUMENT_LOAD_START: "ContentDocumentLoadStart", - /** - * @event - */ - CONTENT_DOCUMENT_LOADED: "ContentDocumentLoaded", - /** - * @event - */ - MEDIA_OVERLAY_STATUS_CHANGED: "MediaOverlayStatusChanged", - /** - * @event - */ - MEDIA_OVERLAY_TTS_SPEAK: "MediaOverlayTTSSpeak", - /** - * @event - */ - MEDIA_OVERLAY_TTS_STOP: "MediaOverlayTTSStop" - }, - /** - * Internal Events - * - * @desc Should not be triggered outside of {@link ReadiumSDK.Views.ReaderView}. - * @namespace - */ - InternalEvents: { - /** - * @event - */ - CURRENT_VIEW_PAGINATION_CHANGED: "CurrentViewPaginationChanged", - } - -}; - - -//This is default implementation of reading system object that will be available for the publication's javascript to analyze at runtime -//To extend/modify/replace this object reading system should subscribe ReadiumSDK.Events.READER_INITIALIZED and apply changes in reaction to this event -navigator.epubReadingSystem = { - name: "", - version: "0.0.0", - layoutStyle: "paginated", - - hasFeature: function (feature, version) { - - // for now all features must be version 1.0 so fail fast if the user has asked for something else - if (version && version !== "1.0") { - return false; - } - - if (feature === "dom-manipulation") { - // Scripts may make structural changes to the document???s DOM (applies to spine-level scripting only). - return true; - } - if (feature === "layout-changes") { - // Scripts may modify attributes and CSS styles that affect content layout (applies to spine-level scripting only). - return true; - } - if (feature === "touch-events") { - // The device supports touch events and the Reading System passes touch events to the content. - return false; - } - if (feature === "mouse-events") { - // The device supports mouse events and the Reading System passes mouse events to the content. - return true; - } - if (feature === "keyboard-events") { - // The device supports keyboard events and the Reading System passes keyboard events to the content. - return true; - } - - if (feature === "spine-scripting") { - //Spine-level scripting is supported. - return true; - } - - return false; - } -}; - - -_.extend(ReadiumSDK, Backbone.Events); - - +// LauncherOSX +// +// Created by Boris Schneiderman. +// Copyright (c) 2014 Readium Foundation and/or its licensees. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// 3. Neither the name of the organization nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. + + +/** + * Top level ReadiumSDK namespace + * @namespace + */ +ReadiumSDK = { + + /** + * Current version of the JS SDK + * @static + * @return {string} version + */ + version: function () { + return "0.8.0"; + }, + /** + * @namespace + */ + Models: { + /** + * @type {object} + */ + Smil: {} + }, + /** + * @namespace + */ + Views: { + /** + * Landscape Orientation + */ + ORIENTATION_LANDSCAPE: "orientation_landscape", + /** + * Portrait Orientation + */ + ORIENTATION_PORTRAIT: "orientation_portrait" + }, + /** + * @namespace + */ + Collections: {}, + /** + * @namespace + */ + Routers: {}, + /** + * @namespace + */ + Helpers: {}, + /** + * @namespace + */ + Overrides: {}, + /** + * @namespace + */ + Events: { + /** + * @event + */ + READER_INITIALIZED: "ReaderInitialized", + /** + * This gets triggered on every page turnover. It includes spine information and such. + * @event + */ + PAGINATION_CHANGED: "PaginationChanged", + /** + * @event + */ + SETTINGS_APPLIED: "SettingsApplied", + /** + * @event + */ + FXL_VIEW_RESIZED: "FXLViewResized", + /** + * @event + */ + READER_VIEW_CREATED: "ReaderViewCreated", + /** + * @event + */ + READER_VIEW_DESTROYED: "ReaderViewDestroyed", + /** + * @event + */ + CONTENT_DOCUMENT_LOAD_START: "ContentDocumentLoadStart", + /** + * @event + */ + CONTENT_DOCUMENT_LOADED: "ContentDocumentLoaded", + /** + * @event + */ + MEDIA_OVERLAY_STATUS_CHANGED: "MediaOverlayStatusChanged", + /** + * @event + */ + MEDIA_OVERLAY_TTS_SPEAK: "MediaOverlayTTSSpeak", + /** + * @event + */ + MEDIA_OVERLAY_TTS_STOP: "MediaOverlayTTSStop" + }, + /** + * Internal Events + * + * @desc Should not be triggered outside of {@link ReadiumSDK.Views.ReaderView}. + * @namespace + */ + InternalEvents: { + /** + * @event + */ + CURRENT_VIEW_PAGINATION_CHANGED: "CurrentViewPaginationChanged", + } + +}; + + +//This is default implementation of reading system object that will be available for the publication's javascript to analyze at runtime +//To extend/modify/replace this object reading system should subscribe ReadiumSDK.Events.READER_INITIALIZED and apply changes in reaction to this event +navigator.epubReadingSystem = { + name: "", + version: "0.0.0", + layoutStyle: "paginated", + + hasFeature: function (feature, version) { + + // for now all features must be version 1.0 so fail fast if the user has asked for something else + if (version && version !== "1.0") { + return false; + } + + if (feature === "dom-manipulation") { + // Scripts may make structural changes to the document???s DOM (applies to spine-level scripting only). + return true; + } + if (feature === "layout-changes") { + // Scripts may modify attributes and CSS styles that affect content layout (applies to spine-level scripting only). + return true; + } + if (feature === "touch-events") { + // The device supports touch events and the Reading System passes touch events to the content. + return false; + } + if (feature === "mouse-events") { + // The device supports mouse events and the Reading System passes mouse events to the content. + return true; + } + if (feature === "keyboard-events") { + // The device supports keyboard events and the Reading System passes keyboard events to the content. + return true; + } + + if (feature === "spine-scripting") { + //Spine-level scripting is supported. + return true; + } + + return false; + } +}; + + +_.extend(ReadiumSDK, Backbone.Events); + + define("readiumSDK", ["backbone"], (function (global) { return function () { @@ -585,83 +585,83 @@ define("readiumSDK", ["backbone"], (function (global) { }; }(this))); -/** - * @preserve JSizes - JQuery plugin v0.33 - * - * Licensed under the revised BSD License. - * Copyright 2008-2010 Bram Stein - * All rights reserved. - */ -/*global jQuery*/ -(function ($) { - var num = function (value) { - return parseInt(value, 10) || 0; - }; - - /** - * Sets or gets the values for min-width, min-height, max-width - * and max-height. - */ - $.each(['min', 'max'], function (i, name) { - $.fn[name + 'Size'] = function (value) { - var width, height; - if (value) { - if (value.width !== undefined) { - this.css(name + '-width', value.width); - } - if (value.height !== undefined) { - this.css(name + '-height', value.height); - } - return this; - } - else { - width = this.css(name + '-width'); - height = this.css(name + '-height'); - // Apparently: - // * Opera returns -1px instead of none - // * IE6 returns undefined instead of none - return {'width': (name === 'max' && (width === undefined || width === 'none' || num(width) === -1) && Number.MAX_VALUE) || num(width), - 'height': (name === 'max' && (height === undefined || height === 'none' || num(height) === -1) && Number.MAX_VALUE) || num(height)}; - } - }; - }); - - /** - * Returns whether or not an element is visible. - */ - $.fn.isVisible = function () { - return this.is(':visible'); - }; - - /** - * Sets or gets the values for border, margin and padding. - */ - $.each(['border', 'margin', 'padding'], function (i, name) { - $.fn[name] = function (value) { - if (value) { - if (value.top !== undefined) { - this.css(name + '-top' + (name === 'border' ? '-width' : ''), value.top); - } - if (value.bottom !== undefined) { - this.css(name + '-bottom' + (name === 'border' ? '-width' : ''), value.bottom); - } - if (value.left !== undefined) { - this.css(name + '-left' + (name === 'border' ? '-width' : ''), value.left); - } - if (value.right !== undefined) { - this.css(name + '-right' + (name === 'border' ? '-width' : ''), value.right); - } - return this; - } - else { - return {top: num(this.css(name + '-top' + (name === 'border' ? '-width' : ''))), - bottom: num(this.css(name + '-bottom' + (name === 'border' ? '-width' : ''))), - left: num(this.css(name + '-left' + (name === 'border' ? '-width' : ''))), - right: num(this.css(name + '-right' + (name === 'border' ? '-width' : '')))}; - } - }; - }); -}(jQuery)); +/** + * @preserve JSizes - JQuery plugin v0.33 + * + * Licensed under the revised BSD License. + * Copyright 2008-2010 Bram Stein + * All rights reserved. + */ +/*global jQuery*/ +(function ($) { + var num = function (value) { + return parseInt(value, 10) || 0; + }; + + /** + * Sets or gets the values for min-width, min-height, max-width + * and max-height. + */ + $.each(['min', 'max'], function (i, name) { + $.fn[name + 'Size'] = function (value) { + var width, height; + if (value) { + if (value.width !== undefined) { + this.css(name + '-width', value.width); + } + if (value.height !== undefined) { + this.css(name + '-height', value.height); + } + return this; + } + else { + width = this.css(name + '-width'); + height = this.css(name + '-height'); + // Apparently: + // * Opera returns -1px instead of none + // * IE6 returns undefined instead of none + return {'width': (name === 'max' && (width === undefined || width === 'none' || num(width) === -1) && Number.MAX_VALUE) || num(width), + 'height': (name === 'max' && (height === undefined || height === 'none' || num(height) === -1) && Number.MAX_VALUE) || num(height)}; + } + }; + }); + + /** + * Returns whether or not an element is visible. + */ + $.fn.isVisible = function () { + return this.is(':visible'); + }; + + /** + * Sets or gets the values for border, margin and padding. + */ + $.each(['border', 'margin', 'padding'], function (i, name) { + $.fn[name] = function (value) { + if (value) { + if (value.top !== undefined) { + this.css(name + '-top' + (name === 'border' ? '-width' : ''), value.top); + } + if (value.bottom !== undefined) { + this.css(name + '-bottom' + (name === 'border' ? '-width' : ''), value.bottom); + } + if (value.left !== undefined) { + this.css(name + '-left' + (name === 'border' ? '-width' : ''), value.left); + } + if (value.right !== undefined) { + this.css(name + '-right' + (name === 'border' ? '-width' : ''), value.right); + } + return this; + } + else { + return {top: num(this.css(name + '-top' + (name === 'border' ? '-width' : ''))), + bottom: num(this.css(name + '-bottom' + (name === 'border' ? '-width' : ''))), + left: num(this.css(name + '-left' + (name === 'border' ? '-width' : ''))), + right: num(this.css(name + '-right' + (name === 'border' ? '-width' : '')))}; + } + }; + }); +}(jQuery)); define("jquerySizes", ["jquery"], (function (global) { return function () { @@ -670,773 +670,773 @@ define("jquerySizes", ["jquery"], (function (global) { }; }(this))); -// LauncherOSX -// -// Created by Boris Schneiderman. -// Copyright (c) 2014 Readium Foundation and/or its licensees. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation and/or -// other materials provided with the distribution. -// 3. Neither the name of the organization nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED -// OF THE POSSIBILITY OF SUCH DAMAGE. - -/** - * - * @param left - * @param top - * @param width - * @param height - * @constructor - */ -ReadiumSDK.Helpers.Rect = function(left, top, width, height) { - - this.left = left; - this.top = top; - this.width = width; - this.height = height; - - this.right = function () { - return this.left + this.width; - }; - - this.bottom = function() { - return this.top + this.height; - }; - - this.isOverlap = function(rect, tolerance) { - - if(tolerance == undefined) { - tolerance = 0; - } - - return !(rect.right() < this.left + tolerance || - rect.left > this.right() - tolerance || - rect.bottom() < this.top + tolerance || - rect.top > this.bottom() - tolerance); - } -}; - -/** - * - * @param $element - * @returns {ReadiumSDK.Helpers.Rect} - */ -//This method treats multicolumn view as one long column and finds the rectangle of the element in this "long" column -//we are not using jQuery Offset() and width()/height() function because for multicolumn rendition_layout it produces rectangle as a bounding box of element that -// reflows between columns this is inconstant and difficult to analyze . -ReadiumSDK.Helpers.Rect.fromElement = function($element) { - - var e; - if (_.isArray($element) || $element instanceof jQuery) - e = $element[0]; - else - e = $element; - // TODODM this is somewhat hacky. Text (range?) elements don't have a position so we have to ask the parent. - if (e.nodeType === 3) - { - e = $element.parent()[0]; - } - - - var offsetLeft = e.offsetLeft; - var offsetTop = e.offsetTop; - var offsetWidth = e.offsetWidth; - var offsetHeight = e.offsetHeight; - - while(e = e.offsetParent) { - offsetLeft += e.offsetLeft; - offsetTop += e.offsetTop; - } - - return new ReadiumSDK.Helpers.Rect(offsetLeft, offsetTop, offsetWidth, offsetHeight); -}; - -ReadiumSDK.Helpers.UpdateHtmlFontSize = function($epubHtml, fontSize){ - - - var factor = fontSize/100; - var win = $epubHtml[0].ownerDocument.defaultView; - var $textblocks = $('p, div, span, h1, h2, h3, h4, h5, h6, li, blockquote, td, pre', $epubHtml); - var originalLineHeight; - - - // need to do two passes because it is possible to have nested text blocks. - // If you change the font size of the parent this will then create an inaccurate - // font size for any children. - for (var i = 0; i < $textblocks.length; i++){ - var ele = $textblocks[i], - fontSizeAttr = ele.getAttribute('data-original-font-size'); - - if (!fontSizeAttr){ - var style = win.getComputedStyle(ele); - var originalFontSize = parseInt(style.fontSize); - originalLineHeight = parseInt(style.lineHeight); - - ele.setAttribute('data-original-font-size', originalFontSize); - // getComputedStyle will not calculate the line-height if the value is 'normal'. In this case parseInt will return NaN - if (originalLineHeight){ - ele.setAttribute('data-original-line-height', originalLineHeight); - } - } - } - - // reset variable so the below logic works. All variables in JS are function scoped. - originalLineHeight = 0; - for (var i = 0; i < $textblocks.length; i++){ - var ele = $textblocks[i], - fontSizeAttr = ele.getAttribute('data-original-font-size'), - lineHeightAttr = ele.getAttribute('data-original-line-height'), - originalFontSize = Number(fontSizeAttr); - - if (lineHeightAttr){ - originalLineHeight = Number(lineHeightAttr); - } - else{ - originalLineHeight = 0; - } - - ele.style.fontSize = (originalFontSize * factor) + 'px'; - if (originalLineHeight){ - ele.style.lineHeight = (originalLineHeight * factor) + 'px'; - } - - } - $epubHtml.css("font-size", fontSize + "%"); -} - - -/** - * - * @param contentRef - * @param sourceFileHref - * @returns {string} - * @constructor - */ -ReadiumSDK.Helpers.ResolveContentRef = function(contentRef, sourceFileHref) { - - if(!sourceFileHref) { - return contentRef; - } - - var sourceParts = sourceFileHref.split("/"); - sourceParts.pop(); //remove source file name - - var pathComponents = contentRef.split("/"); - - while(sourceParts.length > 0 && pathComponents[0] === "..") { - - sourceParts.pop(); - pathComponents.splice(0, 1); - } - - var combined = sourceParts.concat(pathComponents); - - return combined.join("/"); - -}; - - -/** - * - * @param str - * @param suffix - * @returns {boolean} - * @static - */ -ReadiumSDK.Helpers.EndsWith = function (str, suffix) { - return str.indexOf(suffix, str.length - suffix.length) !== -1; -}; - -/** - * - * @param str - * @param suffix - * @returns {boolean} - * @static - */ -ReadiumSDK.Helpers.BeginsWith = function (str, suffix) { - - return str.indexOf(suffix) === 0; -}; - -/** - * - * @param str - * @param toRemove - * @returns {string} - * @static - */ -ReadiumSDK.Helpers.RemoveFromString = function(str, toRemove) { - - var startIx = str.indexOf(toRemove); - - if(startIx == -1) { - return str; - } - - return str.substring(0, startIx) + str.substring(startIx + toRemove.length); -}; - -/** - * - * @param margin - * @param border - * @param padding - * @constructor - */ -ReadiumSDK.Helpers.Margins = function(margin, border, padding) { - - this.margin = margin; - this.border = border; - this.padding = padding; - - this.left = this.margin.left + this.border.left + this.padding.left; - this.right = this.margin.right + this.border.right + this.padding.right; - this.top = this.margin.top + this.border.top + this.padding.top; - this.bottom = this.margin.bottom + this.border.bottom + this.padding.bottom; - - this.width = function() { - return this.left + this.right; - }; - - this.height = function() { - return this.top + this.bottom; - } -}; - -/** - * - * @param $iframe - */ -ReadiumSDK.Helpers.triggerLayout = function($iframe) { - - var doc = $iframe[0].contentDocument; - - if(!doc) { - return; - } - - var ss = undefined; - try - { - ss = doc.styleSheets && doc.styleSheets.length ? doc.styleSheets[0] : undefined; - if (!ss) - { - var style = doc.createElement('style'); - doc.head.appendChild(style); - style.appendChild(doc.createTextNode('')); - ss = style.sheet; - } - - if (ss) - ss.insertRule('body:first-child::before {content:\'READIUM\';color: red;font-weight: bold;}', ss.cssRules.length); - } - catch (ex) - { - console.error(ex); - } - - try - { - var el = doc.createElementNS("http://www.w3.org/1999/xhtml", "style"); - el.appendChild(doc.createTextNode("*{}")); - doc.body.appendChild(el); - doc.body.removeChild(el); - - if (ss) - ss.deleteRule(ss.cssRules.length-1); - } - catch (ex) - { - console.error(ex); - } - - if(doc.body) { - var val = doc.body.offsetTop; // triggers layout - } - -}; - -/** - * - * @param $viewport - * @param spineItem - * @param settings - * @returns {boolean} - */ -//Based on https://docs.google.com/spreadsheet/ccc?key=0AoPMUkQhc4wcdDI0anFvWm96N0xRT184ZE96MXFRdFE&usp=drive_web#gid=0 doc -// Returns falsy and truthy -// true and false mean that the synthetic-spread or single-page is "forced" (to be respected whatever the external conditions) -// 1 and 0 mean that the synthetic-spread or single-page is "not forced" (is allowed to be overriden by external conditions, such as optimum column width / text line number of characters, etc.) -ReadiumSDK.Helpers.deduceSyntheticSpread = function($viewport, spineItem, settings) { - - if(!$viewport || $viewport.length == 0) { - return 0; // non-forced - } - - //http://www.idpf.org/epub/fxl/#property-spread-values - - var rendition_spread = spineItem ? spineItem.getRenditionSpread() : undefined; - - if(rendition_spread === ReadiumSDK.Models.SpineItem.RENDITION_SPREAD_NONE) { - return false; // forced - - //"Reading Systems must not incorporate this spine item in a synthetic spread." - } - - if(settings.syntheticSpread == "double") { - return true; // forced - } - else if(settings.syntheticSpread == "single") { - return false; // forced - } - - if(!spineItem) { - return 0; // non-forced - } - - if(rendition_spread === ReadiumSDK.Models.SpineItem.RENDITION_SPREAD_BOTH) { - return true; // forced - - //"Reading Systems should incorporate this spine item in a synthetic spread regardless of device orientation." - } - - var orientation = ReadiumSDK.Helpers.getOrientation($viewport); - - if(rendition_spread === ReadiumSDK.Models.SpineItem.RENDITION_SPREAD_LANDSCAPE) { - return orientation === ReadiumSDK.Views.ORIENTATION_LANDSCAPE; // forced - - //"Reading Systems should incorporate this spine item in a synthetic spread only when the device is in landscape orientation." - } - - if(rendition_spread === ReadiumSDK.Models.SpineItem.RENDITION_SPREAD_PORTRAIT) { - return orientation === ReadiumSDK.Views.ORIENTATION_PORTRAIT; // forced - - //"Reading Systems should incorporate this spine item in a synthetic spread only when the device is in portrait orientation." - } - - if(!rendition_spread || rendition_spread === ReadiumSDK.Models.SpineItem.RENDITION_SPREAD_AUTO) { - // if no spread set in document and user didn't set in in setting we will do double for landscape - var landscape = orientation === ReadiumSDK.Views.ORIENTATION_LANDSCAPE; - return landscape ? 1 : 0; // non-forced - - //"Reading Systems may use synthetic spreads in specific or all device orientations as part of a display area utilization optimization process." - } - - console.warn("ReadiumSDK.Helpers.deduceSyntheticSpread: spread properties?!"); - return 0; // non-forced -}; - -/** - * - * @param $element - * @returns {ReadiumSDK.Helpers.Rect} - */ -ReadiumSDK.Helpers.Margins.fromElement = function($element) { - return new this($element.margin(), $element.border(), $element.padding()); -}; - -/** - * @returns {ReadiumSDK.Helpers.Rect} - */ -ReadiumSDK.Helpers.Margins.empty = function() { - - return new this({left:0, right:0, top:0, bottom: 0}, {left:0, right:0, top:0, bottom: 0}, {left:0, right:0, top:0, bottom: 0}); - -}; - -/** - * - * @param name - * @param params - * @returns {ReadiumSDK.Helpers.loadTemplate.cache} - */ -ReadiumSDK.Helpers.loadTemplate = function(name, params) { - return ReadiumSDK.Helpers.loadTemplate.cache[name]; -}; - -/** - * - * @type {{fixed_book_frame: string, single_page_frame: string, scrolled_book_frame: string, reflowable_book_frame: string, reflowable_book_page_frame: string}} - */ -ReadiumSDK.Helpers.loadTemplate.cache = { - "fixed_book_frame" : '
', - - "single_page_frame" : '
', - //"single_page_frame" : '
', - - "scrolled_book_frame" : '
', - "reflowable_book_frame" : '
', - "reflowable_book_page_frame": '
' -}; - -/** - * - * @param styles - * @param $element - */ -ReadiumSDK.Helpers.setStyles = function(styles, $element) { - - var count = styles.length; - - if(!count) { - return; - } - - for(var i = 0; i < count; i++) { - var style = styles[i]; - if(style.selector) { - $(style.selector, $element).css(style.declarations); - } - else { - $element.css(style.declarations); - } - } - -}; - -/** - * - * @param iframe - * @returns {boolean} - */ -ReadiumSDK.Helpers.isIframeAlive = function(iframe) -{ - var w = undefined; - var d = undefined; - try - { - w = iframe.contentWindow; - d = iframe.contentDocument; - } - catch (ex) - { - console.error(ex); - return false; - } - - return w && d; -} - -/** - * - * @param $viewport - * @returns {ReadiumSDK.Views.ORIENTATION_LANDSCAPE|ReadiumSDK.Views.ORIENTATION_PORTRAIT} - */ -ReadiumSDK.Helpers.getOrientation = function($viewport) { +// LauncherOSX +// +// Created by Boris Schneiderman. +// Copyright (c) 2014 Readium Foundation and/or its licensees. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// 3. Neither the name of the organization nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. + +/** + * + * @param left + * @param top + * @param width + * @param height + * @constructor + */ +ReadiumSDK.Helpers.Rect = function(left, top, width, height) { + + this.left = left; + this.top = top; + this.width = width; + this.height = height; + + this.right = function () { + return this.left + this.width; + }; + + this.bottom = function() { + return this.top + this.height; + }; + + this.isOverlap = function(rect, tolerance) { + + if(tolerance == undefined) { + tolerance = 0; + } + + return !(rect.right() < this.left + tolerance || + rect.left > this.right() - tolerance || + rect.bottom() < this.top + tolerance || + rect.top > this.bottom() - tolerance); + } +}; + +/** + * + * @param $element + * @returns {ReadiumSDK.Helpers.Rect} + */ +//This method treats multicolumn view as one long column and finds the rectangle of the element in this "long" column +//we are not using jQuery Offset() and width()/height() function because for multicolumn rendition_layout it produces rectangle as a bounding box of element that +// reflows between columns this is inconstant and difficult to analyze . +ReadiumSDK.Helpers.Rect.fromElement = function($element) { + + var e; + if (_.isArray($element) || $element instanceof jQuery) + e = $element[0]; + else + e = $element; + // TODODM this is somewhat hacky. Text (range?) elements don't have a position so we have to ask the parent. + if (e.nodeType === 3) + { + e = $element.parent()[0]; + } + + + var offsetLeft = e.offsetLeft; + var offsetTop = e.offsetTop; + var offsetWidth = e.offsetWidth; + var offsetHeight = e.offsetHeight; + + while(e = e.offsetParent) { + offsetLeft += e.offsetLeft; + offsetTop += e.offsetTop; + } + + return new ReadiumSDK.Helpers.Rect(offsetLeft, offsetTop, offsetWidth, offsetHeight); +}; + +ReadiumSDK.Helpers.UpdateHtmlFontSize = function($epubHtml, fontSize){ + + + var factor = fontSize/100; + var win = $epubHtml[0].ownerDocument.defaultView; + var $textblocks = $('p, div, span, h1, h2, h3, h4, h5, h6, li, blockquote, td, pre', $epubHtml); + var originalLineHeight; + + + // need to do two passes because it is possible to have nested text blocks. + // If you change the font size of the parent this will then create an inaccurate + // font size for any children. + for (var i = 0; i < $textblocks.length; i++){ + var ele = $textblocks[i], + fontSizeAttr = ele.getAttribute('data-original-font-size'); + + if (!fontSizeAttr){ + var style = win.getComputedStyle(ele); + var originalFontSize = parseInt(style.fontSize); + originalLineHeight = parseInt(style.lineHeight); + + ele.setAttribute('data-original-font-size', originalFontSize); + // getComputedStyle will not calculate the line-height if the value is 'normal'. In this case parseInt will return NaN + if (originalLineHeight){ + ele.setAttribute('data-original-line-height', originalLineHeight); + } + } + } + + // reset variable so the below logic works. All variables in JS are function scoped. + originalLineHeight = 0; + for (var i = 0; i < $textblocks.length; i++){ + var ele = $textblocks[i], + fontSizeAttr = ele.getAttribute('data-original-font-size'), + lineHeightAttr = ele.getAttribute('data-original-line-height'), + originalFontSize = Number(fontSizeAttr); + + if (lineHeightAttr){ + originalLineHeight = Number(lineHeightAttr); + } + else{ + originalLineHeight = 0; + } + + ele.style.fontSize = (originalFontSize * factor) + 'px'; + if (originalLineHeight){ + ele.style.lineHeight = (originalLineHeight * factor) + 'px'; + } + + } + $epubHtml.css("font-size", fontSize + "%"); +} + + +/** + * + * @param contentRef + * @param sourceFileHref + * @returns {string} + * @constructor + */ +ReadiumSDK.Helpers.ResolveContentRef = function(contentRef, sourceFileHref) { + + if(!sourceFileHref) { + return contentRef; + } + + var sourceParts = sourceFileHref.split("/"); + sourceParts.pop(); //remove source file name + + var pathComponents = contentRef.split("/"); + + while(sourceParts.length > 0 && pathComponents[0] === "..") { + + sourceParts.pop(); + pathComponents.splice(0, 1); + } + + var combined = sourceParts.concat(pathComponents); + + return combined.join("/"); + +}; + + +/** + * + * @param str + * @param suffix + * @returns {boolean} + * @static + */ +ReadiumSDK.Helpers.EndsWith = function (str, suffix) { + return str.indexOf(suffix, str.length - suffix.length) !== -1; +}; + +/** + * + * @param str + * @param suffix + * @returns {boolean} + * @static + */ +ReadiumSDK.Helpers.BeginsWith = function (str, suffix) { + + return str.indexOf(suffix) === 0; +}; + +/** + * + * @param str + * @param toRemove + * @returns {string} + * @static + */ +ReadiumSDK.Helpers.RemoveFromString = function(str, toRemove) { + + var startIx = str.indexOf(toRemove); + + if(startIx == -1) { + return str; + } + + return str.substring(0, startIx) + str.substring(startIx + toRemove.length); +}; + +/** + * + * @param margin + * @param border + * @param padding + * @constructor + */ +ReadiumSDK.Helpers.Margins = function(margin, border, padding) { + + this.margin = margin; + this.border = border; + this.padding = padding; + + this.left = this.margin.left + this.border.left + this.padding.left; + this.right = this.margin.right + this.border.right + this.padding.right; + this.top = this.margin.top + this.border.top + this.padding.top; + this.bottom = this.margin.bottom + this.border.bottom + this.padding.bottom; + + this.width = function() { + return this.left + this.right; + }; + + this.height = function() { + return this.top + this.bottom; + } +}; + +/** + * + * @param $iframe + */ +ReadiumSDK.Helpers.triggerLayout = function($iframe) { + + var doc = $iframe[0].contentDocument; + + if(!doc) { + return; + } + + var ss = undefined; + try + { + ss = doc.styleSheets && doc.styleSheets.length ? doc.styleSheets[0] : undefined; + if (!ss) + { + var style = doc.createElement('style'); + doc.head.appendChild(style); + style.appendChild(doc.createTextNode('')); + ss = style.sheet; + } + + if (ss) + ss.insertRule('body:first-child::before {content:\'READIUM\';color: red;font-weight: bold;}', ss.cssRules.length); + } + catch (ex) + { + console.error(ex); + } + + try + { + var el = doc.createElementNS("http://www.w3.org/1999/xhtml", "style"); + el.appendChild(doc.createTextNode("*{}")); + doc.body.appendChild(el); + doc.body.removeChild(el); + + if (ss) + ss.deleteRule(ss.cssRules.length-1); + } + catch (ex) + { + console.error(ex); + } + + if(doc.body) { + var val = doc.body.offsetTop; // triggers layout + } + +}; + +/** + * + * @param $viewport + * @param spineItem + * @param settings + * @returns {boolean} + */ +//Based on https://docs.google.com/spreadsheet/ccc?key=0AoPMUkQhc4wcdDI0anFvWm96N0xRT184ZE96MXFRdFE&usp=drive_web#gid=0 doc +// Returns falsy and truthy +// true and false mean that the synthetic-spread or single-page is "forced" (to be respected whatever the external conditions) +// 1 and 0 mean that the synthetic-spread or single-page is "not forced" (is allowed to be overriden by external conditions, such as optimum column width / text line number of characters, etc.) +ReadiumSDK.Helpers.deduceSyntheticSpread = function($viewport, spineItem, settings) { + + if(!$viewport || $viewport.length == 0) { + return 0; // non-forced + } + + //http://www.idpf.org/epub/fxl/#property-spread-values + + var rendition_spread = spineItem ? spineItem.getRenditionSpread() : undefined; + + if(rendition_spread === ReadiumSDK.Models.SpineItem.RENDITION_SPREAD_NONE) { + return false; // forced + + //"Reading Systems must not incorporate this spine item in a synthetic spread." + } + + if(settings.syntheticSpread == "double") { + return true; // forced + } + else if(settings.syntheticSpread == "single") { + return false; // forced + } + + if(!spineItem) { + return 0; // non-forced + } + + if(rendition_spread === ReadiumSDK.Models.SpineItem.RENDITION_SPREAD_BOTH) { + return true; // forced + + //"Reading Systems should incorporate this spine item in a synthetic spread regardless of device orientation." + } + + var orientation = ReadiumSDK.Helpers.getOrientation($viewport); + + if(rendition_spread === ReadiumSDK.Models.SpineItem.RENDITION_SPREAD_LANDSCAPE) { + return orientation === ReadiumSDK.Views.ORIENTATION_LANDSCAPE; // forced + + //"Reading Systems should incorporate this spine item in a synthetic spread only when the device is in landscape orientation." + } + + if(rendition_spread === ReadiumSDK.Models.SpineItem.RENDITION_SPREAD_PORTRAIT) { + return orientation === ReadiumSDK.Views.ORIENTATION_PORTRAIT; // forced + + //"Reading Systems should incorporate this spine item in a synthetic spread only when the device is in portrait orientation." + } + + if(!rendition_spread || rendition_spread === ReadiumSDK.Models.SpineItem.RENDITION_SPREAD_AUTO) { + // if no spread set in document and user didn't set in in setting we will do double for landscape + var landscape = orientation === ReadiumSDK.Views.ORIENTATION_LANDSCAPE; + return landscape ? 1 : 0; // non-forced + + //"Reading Systems may use synthetic spreads in specific or all device orientations as part of a display area utilization optimization process." + } + + console.warn("ReadiumSDK.Helpers.deduceSyntheticSpread: spread properties?!"); + return 0; // non-forced +}; + +/** + * + * @param $element + * @returns {ReadiumSDK.Helpers.Rect} + */ +ReadiumSDK.Helpers.Margins.fromElement = function($element) { + return new this($element.margin(), $element.border(), $element.padding()); +}; + +/** + * @returns {ReadiumSDK.Helpers.Rect} + */ +ReadiumSDK.Helpers.Margins.empty = function() { + + return new this({left:0, right:0, top:0, bottom: 0}, {left:0, right:0, top:0, bottom: 0}, {left:0, right:0, top:0, bottom: 0}); + +}; + +/** + * + * @param name + * @param params + * @returns {ReadiumSDK.Helpers.loadTemplate.cache} + */ +ReadiumSDK.Helpers.loadTemplate = function(name, params) { + return ReadiumSDK.Helpers.loadTemplate.cache[name]; +}; + +/** + * + * @type {{fixed_book_frame: string, single_page_frame: string, scrolled_book_frame: string, reflowable_book_frame: string, reflowable_book_page_frame: string}} + */ +ReadiumSDK.Helpers.loadTemplate.cache = { + "fixed_book_frame" : '
', + + "single_page_frame" : '
', + //"single_page_frame" : '
', + + "scrolled_book_frame" : '
', + "reflowable_book_frame" : '
', + "reflowable_book_page_frame": '
' +}; + +/** + * + * @param styles + * @param $element + */ +ReadiumSDK.Helpers.setStyles = function(styles, $element) { + + var count = styles.length; + + if(!count) { + return; + } + + for(var i = 0; i < count; i++) { + var style = styles[i]; + if(style.selector) { + $(style.selector, $element).css(style.declarations); + } + else { + $element.css(style.declarations); + } + } + +}; + +/** + * + * @param iframe + * @returns {boolean} + */ +ReadiumSDK.Helpers.isIframeAlive = function(iframe) +{ + var w = undefined; + var d = undefined; + try + { + w = iframe.contentWindow; + d = iframe.contentDocument; + } + catch (ex) + { + console.error(ex); + return false; + } + + return w && d; +} + +/** + * + * @param $viewport + * @returns {ReadiumSDK.Views.ORIENTATION_LANDSCAPE|ReadiumSDK.Views.ORIENTATION_PORTRAIT} + */ +ReadiumSDK.Helpers.getOrientation = function($viewport) { + + var viewportWidth = $viewport.width(); + var viewportHeight = $viewport.height(); + + if(!viewportWidth || !viewportHeight) { + return undefined; + } + + return viewportWidth >= viewportHeight ? ReadiumSDK.Views.ORIENTATION_LANDSCAPE : ReadiumSDK.Views.ORIENTATION_PORTRAIT; +}; + +/** + * + * @param item + * @param orientation + * @returns {boolean} + */ +ReadiumSDK.Helpers.isRenditionSpreadPermittedForItem = function(item, orientation) { + + var rendition_spread = item.getRenditionSpread(); + + return !rendition_spread + || rendition_spread == ReadiumSDK.Models.SpineItem.RENDITION_SPREAD_BOTH + || rendition_spread == ReadiumSDK.Models.SpineItem.RENDITION_SPREAD_AUTO + || (rendition_spread == ReadiumSDK.Models.SpineItem.RENDITION_SPREAD_LANDSCAPE + && orientation == ReadiumSDK.Views.ORIENTATION_LANDSCAPE) + || (rendition_spread == ReadiumSDK.Models.SpineItem.RENDITION_SPREAD_PORTRAIT + && orientation == ReadiumSDK.Views.ORIENTATION_PORTRAIT ); +}; + +ReadiumSDK.Helpers.CSSTransition = function($el, trans) { + + // does not work! + //$el.css('transition', trans); + + var css={}; + // empty '' prefix FIRST! + _.each(['', '-webkit-', '-moz-', '-ms-'], function(prefix) { + css[prefix + 'transition'] = prefix + trans; + }); + $el.css(css); +} + +//scale, left, top, angle, origin +ReadiumSDK.Helpers.CSSTransformString = function(options) { + var enable3D = options.enable3D ? true : false; + + var translate, scale, rotation, + origin = options.origin; + + if (options.left || options.top){ + var left = options.left || 0, + top = options.top || 0; + + translate = enable3D ? ("translate3D(" + left + "px, " + top + "px, 0)") : ("translate(" + left + "px, " + top + "px)"); + } + if (options.scale){ + scale = enable3D ? ("scale3D(" + options.scale + ", " + options.scale + ", 0)") : ("scale(" + options.scale + ")"); + } + if (options.angle){ + rotation = enable3D ? ("rotate3D(0,0," + options.angle + "deg)") : ("rotate(" + options.angle + "deg)"); + } + + if (!(translate || scale || rotation)){ + return {}; + } + + var transformString = (translate && scale) ? (translate + " " + scale) : (translate ? translate : scale); // the order is important! + if (rotation) + { + transformString = transformString + " " + rotation; + //transformString = rotation + " " + transformString; + } + + var css = {}; + css['transform'] = transformString; + css['transform-origin'] = origin ? origin : (enable3D ? '0 0 0' : '0 0'); + return css; +}; + +ReadiumSDK.Helpers.extendedThrottle = function (startCb, tickCb, endCb, tickRate, waitThreshold, context) { + if (!tickRate) tickRate = 250; + if (!waitThreshold) waitThreshold = tickRate; + + var first = true, + last, + deferTimer; + + return function () { + var ctx = context || this, + now = (Date.now && Date.now()) || new Date().getTime(), + args = arguments; + + if (!(last && now < last + tickRate)) { + last = now; + if (first) { + startCb.apply(ctx, args); + first = false; + } else { + tickCb.apply(ctx, args); + } + } + + clearTimeout(deferTimer); + deferTimer = setTimeout(function () { + last = now; + first = true; + endCb.apply(ctx, args); + }, waitThreshold); + }; +}; + + +//TODO: consider using CSSOM escape() or polyfill +//https://github.com/mathiasbynens/CSS.escape/blob/master/css.escape.js +//http://mathiasbynens.be/notes/css-escapes +/** + * + * @param sel + * @returns {string} + */ +ReadiumSDK.Helpers.escapeJQuerySelector = function(sel) { + //http://api.jquery.com/category/selectors/ + //!"#$%&'()*+,./:;<=>?@[\]^`{|}~ + // double backslash escape + + if (!sel) return undefined; + + var selector = sel.replace(/([;&,\.\+\*\~\?':"\!\^#$%@\[\]\(\)<=>\|\/\\{}`])/g, '\\$1'); + + // if (selector !== sel) + // { + // console.debug("---- SELECTOR ESCAPED"); + // console.debug("1: " + sel); + // console.debug("2: " + selector); + // } + // else + // { + // console.debug("---- SELECTOR OKAY: " + sel); + // } + + return selector; +}; + // TESTS BELOW ALL WORKING FINE :) + // (RegExp typos are hard to spot!) + // escapeSelector('!'); + // escapeSelector('"'); + // escapeSelector('#'); + // escapeSelector('$'); + // escapeSelector('%'); + // escapeSelector('&'); + // escapeSelector("'"); + // escapeSelector('('); + // escapeSelector(')'); + // escapeSelector('*'); + // escapeSelector('+'); + // escapeSelector(','); + // escapeSelector('.'); + // escapeSelector('/'); + // escapeSelector(':'); + // escapeSelector(';'); + // escapeSelector('<'); + // escapeSelector('='); + // escapeSelector('>'); + // escapeSelector('?'); + // escapeSelector('@'); + // escapeSelector('['); + // escapeSelector('\\'); + // escapeSelector(']'); + // escapeSelector('^'); + // escapeSelector('`'); + // escapeSelector('{'); + // escapeSelector('|'); + // escapeSelector('}'); + // escapeSelector('~'); - var viewportWidth = $viewport.width(); - var viewportHeight = $viewport.height(); +define("helpers", ["readiumSDK","jquerySizes"], (function (global) { + return function () { + var ret, fn; + return ret || global.helpers; + }; +}(this))); - if(!viewportWidth || !viewportHeight) { - return undefined; - } - - return viewportWidth >= viewportHeight ? ReadiumSDK.Views.ORIENTATION_LANDSCAPE : ReadiumSDK.Views.ORIENTATION_PORTRAIT; -}; - -/** - * - * @param item - * @param orientation - * @returns {boolean} - */ -ReadiumSDK.Helpers.isRenditionSpreadPermittedForItem = function(item, orientation) { - - var rendition_spread = item.getRenditionSpread(); - - return !rendition_spread - || rendition_spread == ReadiumSDK.Models.SpineItem.RENDITION_SPREAD_BOTH - || rendition_spread == ReadiumSDK.Models.SpineItem.RENDITION_SPREAD_AUTO - || (rendition_spread == ReadiumSDK.Models.SpineItem.RENDITION_SPREAD_LANDSCAPE - && orientation == ReadiumSDK.Views.ORIENTATION_LANDSCAPE) - || (rendition_spread == ReadiumSDK.Models.SpineItem.RENDITION_SPREAD_PORTRAIT - && orientation == ReadiumSDK.Views.ORIENTATION_PORTRAIT ); -}; - -ReadiumSDK.Helpers.CSSTransition = function($el, trans) { - - // does not work! - //$el.css('transition', trans); - - var css={}; - // empty '' prefix FIRST! - _.each(['', '-webkit-', '-moz-', '-ms-'], function(prefix) { - css[prefix + 'transition'] = prefix + trans; - }); - $el.css(css); -} - -//scale, left, top, angle, origin -ReadiumSDK.Helpers.CSSTransformString = function(options) { - var enable3D = options.enable3D ? true : false; - - var translate, scale, rotation, - origin = options.origin; - - if (options.left || options.top){ - var left = options.left || 0, - top = options.top || 0; - - translate = enable3D ? ("translate3D(" + left + "px, " + top + "px, 0)") : ("translate(" + left + "px, " + top + "px)"); - } - if (options.scale){ - scale = enable3D ? ("scale3D(" + options.scale + ", " + options.scale + ", 0)") : ("scale(" + options.scale + ")"); - } - if (options.angle){ - rotation = enable3D ? ("rotate3D(0,0," + options.angle + "deg)") : ("rotate(" + options.angle + "deg)"); - } - - if (!(translate || scale || rotation)){ - return {}; - } - - var transformString = (translate && scale) ? (translate + " " + scale) : (translate ? translate : scale); // the order is important! - if (rotation) - { - transformString = transformString + " " + rotation; - //transformString = rotation + " " + transformString; - } - - var css = {}; - css['transform'] = transformString; - css['transform-origin'] = origin ? origin : (enable3D ? '0 0 0' : '0 0'); - return css; -}; - -ReadiumSDK.Helpers.extendedThrottle = function (startCb, tickCb, endCb, tickRate, waitThreshold, context) { - if (!tickRate) tickRate = 250; - if (!waitThreshold) waitThreshold = tickRate; - - var first = true, - last, - deferTimer; - - return function () { - var ctx = context || this, - now = (Date.now && Date.now()) || new Date().getTime(), - args = arguments; - - if (!(last && now < last + tickRate)) { - last = now; - if (first) { - startCb.apply(ctx, args); - first = false; - } else { - tickCb.apply(ctx, args); - } - } - - clearTimeout(deferTimer); - deferTimer = setTimeout(function () { - last = now; - first = true; - endCb.apply(ctx, args); - }, waitThreshold); - }; -}; - - -//TODO: consider using CSSOM escape() or polyfill -//https://github.com/mathiasbynens/CSS.escape/blob/master/css.escape.js -//http://mathiasbynens.be/notes/css-escapes -/** - * - * @param sel - * @returns {string} - */ -ReadiumSDK.Helpers.escapeJQuerySelector = function(sel) { - //http://api.jquery.com/category/selectors/ - //!"#$%&'()*+,./:;<=>?@[\]^`{|}~ - // double backslash escape - - if (!sel) return undefined; - - var selector = sel.replace(/([;&,\.\+\*\~\?':"\!\^#$%@\[\]\(\)<=>\|\/\\{}`])/g, '\\$1'); - - // if (selector !== sel) - // { - // console.debug("---- SELECTOR ESCAPED"); - // console.debug("1: " + sel); - // console.debug("2: " + selector); - // } - // else - // { - // console.debug("---- SELECTOR OKAY: " + sel); - // } - - return selector; -}; - // TESTS BELOW ALL WORKING FINE :) - // (RegExp typos are hard to spot!) - // escapeSelector('!'); - // escapeSelector('"'); - // escapeSelector('#'); - // escapeSelector('$'); - // escapeSelector('%'); - // escapeSelector('&'); - // escapeSelector("'"); - // escapeSelector('('); - // escapeSelector(')'); - // escapeSelector('*'); - // escapeSelector('+'); - // escapeSelector(','); - // escapeSelector('.'); - // escapeSelector('/'); - // escapeSelector(':'); - // escapeSelector(';'); - // escapeSelector('<'); - // escapeSelector('='); - // escapeSelector('>'); - // escapeSelector('?'); - // escapeSelector('@'); - // escapeSelector('['); - // escapeSelector('\\'); - // escapeSelector(']'); - // escapeSelector('^'); - // escapeSelector('`'); - // escapeSelector('{'); - // escapeSelector('|'); - // escapeSelector('}'); - // escapeSelector('~'); - -define("helpers", ["readiumSDK","jquerySizes"], (function (global) { - return function () { - var ret, fn; - return ret || global.helpers; - }; -}(this))); - -// Created by Boris Schneiderman. -// Copyright (c) 2014 Readium Foundation and/or its licensees. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation and/or -// other materials provided with the distribution. -// 3. Neither the name of the organization nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED -// OF THE POSSIBILITY OF SUCH DAMAGE. - -/** - * - * @param settingsData - * @constructor - */ -ReadiumSDK.Models.ViewerSettings = function(settingsData) { - - var self = this; - - this.syntheticSpread = "auto"; - this.fontSize = 100; - this.columnGap = 20; - - this.mediaOverlaysPreservePlaybackWhenScroll = false; - - this.mediaOverlaysSkipSkippables = false; - this.mediaOverlaysEscapeEscapables = true; - - this.mediaOverlaysSkippables = []; - this.mediaOverlaysEscapables = []; - - this.mediaOverlaysEnableClick = true; - this.mediaOverlaysRate = 1; - this.mediaOverlaysVolume = 100; - - this.mediaOverlaysSynchronizationGranularity = ""; - - this.mediaOverlaysAutomaticPageTurn = true; - - this.enableGPUHardwareAccelerationCSS3D = false; - - // -1 ==> disable - // [0...n] ==> index of transition in pre-defined array - this.pageTransition = -1; - - this.scroll = "auto"; - - function buildArray(str) - { - var retArr = []; - var arr = str.split(/[\s,;]+/); //',' - for (var i = 0; i < arr.length; i++) - { - var item = arr[i].trim(); - if (item !== "") - { - retArr.push(item); - } - } - return retArr; - } - - function mapProperty(propName, settingsData, functionToApply) { - - if(settingsData[propName] !== undefined) { - if(functionToApply) { - - self[propName] = functionToApply(settingsData[propName]); - } - else { - self[propName] = settingsData[propName]; - } - } - - } - - this.update = function(settingsData) { - - mapProperty("columnGap", settingsData); - mapProperty("fontSize", settingsData); - mapProperty("mediaOverlaysPreservePlaybackWhenScroll", settingsData); - mapProperty("mediaOverlaysSkipSkippables", settingsData); - mapProperty("mediaOverlaysEscapeEscapables", settingsData); - mapProperty("mediaOverlaysSkippables", settingsData, buildArray); - mapProperty("mediaOverlaysEscapables", settingsData, buildArray); - mapProperty("mediaOverlaysEnableClick", settingsData); - mapProperty("mediaOverlaysRate", settingsData); - mapProperty("mediaOverlaysVolume", settingsData); - mapProperty("mediaOverlaysSynchronizationGranularity", settingsData); - mapProperty("mediaOverlaysAutomaticPageTurn", settingsData); - mapProperty("scroll", settingsData); - mapProperty("syntheticSpread", settingsData); - mapProperty("pageTransition", settingsData); - mapProperty("enableGPUHardwareAccelerationCSS3D", settingsData); - }; - - this.update(settingsData); -}; +// Created by Boris Schneiderman. +// Copyright (c) 2014 Readium Foundation and/or its licensees. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// 3. Neither the name of the organization nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. + +/** + * + * @param settingsData + * @constructor + */ +ReadiumSDK.Models.ViewerSettings = function(settingsData) { + + var self = this; + + this.syntheticSpread = "auto"; + this.fontSize = 100; + this.columnGap = 20; + + this.mediaOverlaysPreservePlaybackWhenScroll = false; + + this.mediaOverlaysSkipSkippables = false; + this.mediaOverlaysEscapeEscapables = true; + + this.mediaOverlaysSkippables = []; + this.mediaOverlaysEscapables = []; + + this.mediaOverlaysEnableClick = true; + this.mediaOverlaysRate = 1; + this.mediaOverlaysVolume = 100; + + this.mediaOverlaysSynchronizationGranularity = ""; + + this.mediaOverlaysAutomaticPageTurn = true; + + this.enableGPUHardwareAccelerationCSS3D = false; + + // -1 ==> disable + // [0...n] ==> index of transition in pre-defined array + this.pageTransition = -1; + + this.scroll = "auto"; + + function buildArray(str) + { + var retArr = []; + var arr = str.split(/[\s,;]+/); //',' + for (var i = 0; i < arr.length; i++) + { + var item = arr[i].trim(); + if (item !== "") + { + retArr.push(item); + } + } + return retArr; + } + + function mapProperty(propName, settingsData, functionToApply) { + + if(settingsData[propName] !== undefined) { + if(functionToApply) { + + self[propName] = functionToApply(settingsData[propName]); + } + else { + self[propName] = settingsData[propName]; + } + } + + } + + this.update = function(settingsData) { + + mapProperty("columnGap", settingsData); + mapProperty("fontSize", settingsData); + mapProperty("mediaOverlaysPreservePlaybackWhenScroll", settingsData); + mapProperty("mediaOverlaysSkipSkippables", settingsData); + mapProperty("mediaOverlaysEscapeEscapables", settingsData); + mapProperty("mediaOverlaysSkippables", settingsData, buildArray); + mapProperty("mediaOverlaysEscapables", settingsData, buildArray); + mapProperty("mediaOverlaysEnableClick", settingsData); + mapProperty("mediaOverlaysRate", settingsData); + mapProperty("mediaOverlaysVolume", settingsData); + mapProperty("mediaOverlaysSynchronizationGranularity", settingsData); + mapProperty("mediaOverlaysAutomaticPageTurn", settingsData); + mapProperty("scroll", settingsData); + mapProperty("syntheticSpread", settingsData); + mapProperty("pageTransition", settingsData); + mapProperty("enableGPUHardwareAccelerationCSS3D", settingsData); + }; + + this.update(settingsData); +}; define("viewerSettings", ["readiumSDK"], (function (global) { return function () { @@ -1445,52 +1445,52 @@ define("viewerSettings", ["readiumSDK"], (function (global) { }; }(this))); -// Created by Boris Schneiderman. -// Copyright (c) 2014 Readium Foundation and/or its licensees. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation and/or -// other materials provided with the distribution. -// 3. Neither the name of the organization nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED -// OF THE POSSIBILITY OF SUCH DAMAGE. - -/** - * - * @param selector - * @param declarations - * @constructor - */ -ReadiumSDK.Models.Style = function(selector, declarations) { - - this.selector = selector; - this.declarations = declarations; - - this.setDeclarations = function(declarations) { - - for(var prop in declarations) { - if(declarations.hasOwnProperty(prop)) { - this.declarations[prop] = declarations[prop]; - } - } - - } -}; +// Created by Boris Schneiderman. +// Copyright (c) 2014 Readium Foundation and/or its licensees. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// 3. Neither the name of the organization nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. + +/** + * + * @param selector + * @param declarations + * @constructor + */ +ReadiumSDK.Models.Style = function(selector, declarations) { + + this.selector = selector; + this.declarations = declarations; + + this.setDeclarations = function(declarations) { + + for(var prop in declarations) { + if(declarations.hasOwnProperty(prop)) { + this.declarations[prop] = declarations[prop]; + } + } + + } +}; define("style", ["readiumSDK"], (function (global) { return function () { @@ -1499,106 +1499,106 @@ define("style", ["readiumSDK"], (function (global) { }; }(this))); -// Created by Boris Schneiderman. -// Copyright (c) 2014 Readium Foundation and/or its licensees. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation and/or -// other materials provided with the distribution. -// 3. Neither the name of the organization nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED -// OF THE POSSIBILITY OF SUCH DAMAGE. - -/** - * - * @constructor - */ -ReadiumSDK.Collections.StyleCollection = function() { - - var _styles = []; - - this.clear = function() { - _styles.length = 0; - - }; - - this.findStyle = function(selector) { - - var count = _styles.length; - for(var i = 0; i < count; i++) { - if(_styles[i].selector === selector) { - return _styles[i]; - } - } - - return undefined; - }; - - this.addStyle = function(selector, declarations) { - - var style = this.findStyle(selector); - - if(style) { - style.setDeclarations(declarations); - } - else { - style = new ReadiumSDK.Models.Style(selector, declarations); - _styles.push(style); - } - - return style; - }; - - this.removeStyle = function(selector) { - - var count = _styles.length; - - for(var i = 0; i < count; i++) { - - if(_styles[i].selector === selector) { - _styles.splice(i, 1); - return; - } - } - }; - - this.getStyles = function() { - return _styles; - }; - - this.resetStyleValues = function() { - - var count = _styles.length; - - for(var i = 0; i < count; i++) { - - var style = _styles[i]; - var declarations = style.declarations; - - for(var prop in declarations) { - if(declarations.hasOwnProperty(prop)) { - declarations[prop] = ''; - } - } - } - } - -}; +// Created by Boris Schneiderman. +// Copyright (c) 2014 Readium Foundation and/or its licensees. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// 3. Neither the name of the organization nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. + +/** + * + * @constructor + */ +ReadiumSDK.Collections.StyleCollection = function() { + + var _styles = []; + + this.clear = function() { + _styles.length = 0; + + }; + + this.findStyle = function(selector) { + + var count = _styles.length; + for(var i = 0; i < count; i++) { + if(_styles[i].selector === selector) { + return _styles[i]; + } + } + + return undefined; + }; + + this.addStyle = function(selector, declarations) { + + var style = this.findStyle(selector); + + if(style) { + style.setDeclarations(declarations); + } + else { + style = new ReadiumSDK.Models.Style(selector, declarations); + _styles.push(style); + } + + return style; + }; + + this.removeStyle = function(selector) { + + var count = _styles.length; + + for(var i = 0; i < count; i++) { + + if(_styles[i].selector === selector) { + _styles.splice(i, 1); + return; + } + } + }; + + this.getStyles = function() { + return _styles; + }; + + this.resetStyleValues = function() { + + var count = _styles.length; + + for(var i = 0; i < count; i++) { + + var style = _styles[i]; + var declarations = style.declarations; + + for(var prop in declarations) { + if(declarations.hasOwnProperty(prop)) { + declarations[prop] = ''; + } + } + } + } + +}; define("styleCollection", ["readiumSDK","style"], (function (global) { return function () { @@ -1607,28224 +1607,28358 @@ define("styleCollection", ["readiumSDK","style"], (function (global) { }; }(this))); -// Created by Boris Schneiderman. -// Copyright (c) 2014 Readium Foundation and/or its licensees. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation and/or -// other materials provided with the distribution. -// 3. Neither the name of the organization nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED -// OF THE POSSIBILITY OF SUCH DAMAGE. - -/** - * Wrapper of the SpineItem object received from the host application - * - * @class ReadiumSDK.Models.SpineItem - * - * @param itemData spine item properties container - * @param {Number} index - * @param {ReadiumSDK.Models.Spine} spine - * - */ -ReadiumSDK.Models.SpineItem = function(itemData, index, spine){ - - var self = this; - - this.idref = itemData.idref; - this.href = itemData.href; - - this.linear = itemData.linear ? itemData.linear.toLowerCase() : itemData.linear; - - this.page_spread = itemData.page_spread; - - this.rendition_viewport = itemData.rendition_viewport; - - this.rendition_spread = itemData.rendition_spread; - - //TODO: unused yet! - this.rendition_orientation = itemData.rendition_orientation; - - this.rendition_layout = itemData.rendition_layout; - - this.rendition_flow = itemData.rendition_flow; - - - - this.media_overlay_id = itemData.media_overlay_id; - - this.media_type = itemData.media_type; - - this.index = index; - this.spine = spine; - - validateSpread(); - - this.setSpread = function(spread) { - this.page_spread = spread; - - validateSpread(); - }; - - this.isRenditionSpreadAllowed = function() { - - var rendition_spread = self.getRenditionSpread(); - return !rendition_spread || rendition_spread != ReadiumSDK.Models.SpineItem.RENDITION_SPREAD_NONE; - }; - - function validateSpread() { - - if(!self.page_spread) { - return; - } - - if( self.page_spread != ReadiumSDK.Models.SpineItem.SPREAD_LEFT && - self.page_spread != ReadiumSDK.Models.SpineItem.SPREAD_RIGHT && - self.page_spread != ReadiumSDK.Models.SpineItem.SPREAD_CENTER ) { - - console.error(self.page_spread + " is not a recognized spread type"); - } - - } - - this.isLeftPage = function() { - return self.page_spread == ReadiumSDK.Models.SpineItem.SPREAD_LEFT; - }; - - this.isRightPage = function() { - return self.page_spread == ReadiumSDK.Models.SpineItem.SPREAD_RIGHT; - }; - - this.isCenterPage = function() { - return self.page_spread == ReadiumSDK.Models.SpineItem.SPREAD_CENTER; - }; - - this.isReflowable = function() { - return !self.isFixedLayout(); - }; - - this.isFixedLayout = function() { - - // cannot use isPropertyValueSetForItemOrPackage() here! - - var isLayoutExplicitlyDefined = self.getRenditionLayout(); - - if(isLayoutExplicitlyDefined) { - - if (self.rendition_layout) - { - if (self.rendition_layout === ReadiumSDK.Models.SpineItem.RENDITION_LAYOUT_PREPAGINATED) return true; - if (self.rendition_layout === ReadiumSDK.Models.SpineItem.RENDITION_LAYOUT_REFLOWABLE) return false; - } - - return self.spine.package.isFixedLayout(); - } - - // if image or svg use fixed layout - return self.media_type.indexOf("image/") >= 0; - - }; - - this.getRenditionFlow = function() { - - if(self.rendition_flow) { - return self.rendition_flow; - } - - return self.spine.package.rendition_flow; - }; - - this.getRenditionViewport = function() { - - if(self.rendition_viewport) { - return self.rendition_viewport; - } - - return self.spine.package.rendition_viewport; - }; - - this.getRenditionSpread = function() { - - if(self.rendition_spread) { - return self.rendition_spread; - } - - return self.spine.package.rendition_spread; - }; - - this.getRenditionOrientation = function() { - - if(self.rendition_orientation) { - return self.rendition_orientation; - } - - return self.spine.package.rendition_orientation; - }; - - this.getRenditionLayout = function() { - - if(self.rendition_layout) { - return self.rendition_layout; - } - - return self.spine.package.rendition_layout; - }; - - function isPropertyValueSetForItemOrPackage(propName, propValue) { - - if(self[propName]) { - return self[propName] === propValue; - } - - if(self.spine.package[propName]) { - return self.spine.package[propName] === propValue; - } - - return false; - } - - this.isFlowScrolledContinuous = function() { - - return isPropertyValueSetForItemOrPackage("rendition_flow", ReadiumSDK.Models.SpineItem.RENDITION_FLOW_SCROLLED_CONTINUOUS); - }; - - this.isFlowScrolledDoc = function() { - - return isPropertyValueSetForItemOrPackage("rendition_flow", ReadiumSDK.Models.SpineItem.RENDITION_FLOW_SCROLLED_DOC); - }; -}; - -ReadiumSDK.Models.SpineItem.RENDITION_LAYOUT_REFLOWABLE = "reflowable"; -ReadiumSDK.Models.SpineItem.RENDITION_LAYOUT_PREPAGINATED = "pre-paginated"; - -ReadiumSDK.Models.SpineItem.RENDITION_ORIENTATION_LANDSCAPE = "landscape"; -ReadiumSDK.Models.SpineItem.RENDITION_ORIENTATION_PORTRAIT = "portrait"; -ReadiumSDK.Models.SpineItem.RENDITION_ORIENTATION_AUTO = "auto"; - -ReadiumSDK.Models.SpineItem.SPREAD_LEFT = "page-spread-left"; -ReadiumSDK.Models.SpineItem.SPREAD_RIGHT = "page-spread-right"; -ReadiumSDK.Models.SpineItem.SPREAD_CENTER = "page-spread-center"; - -ReadiumSDK.Models.SpineItem.RENDITION_SPREAD_NONE = "none"; -ReadiumSDK.Models.SpineItem.RENDITION_SPREAD_LANDSCAPE = "landscape"; -ReadiumSDK.Models.SpineItem.RENDITION_SPREAD_PORTRAIT = "portrait"; -ReadiumSDK.Models.SpineItem.RENDITION_SPREAD_BOTH = "both"; -ReadiumSDK.Models.SpineItem.RENDITION_SPREAD_AUTO = "auto"; - -ReadiumSDK.Models.SpineItem.RENDITION_FLOW_PAGINATED = "paginated"; -ReadiumSDK.Models.SpineItem.RENDITION_FLOW_SCROLLED_CONTINUOUS = "scrolled-continuous"; -ReadiumSDK.Models.SpineItem.RENDITION_FLOW_SCROLLED_DOC = "scrolled-doc"; -ReadiumSDK.Models.SpineItem.RENDITION_FLOW_AUTO = "auto"; - -ReadiumSDK.Models.SpineItem.alternateSpread = function(spread) { - - if(spread === ReadiumSDK.Models.SpineItem.SPREAD_LEFT) { - return ReadiumSDK.Models.SpineItem.SPREAD_RIGHT; - } - - if(spread === ReadiumSDK.Models.SpineItem.SPREAD_RIGHT) { - return ReadiumSDK.Models.SpineItem.SPREAD_LEFT; - } - - return spread; - -}; - - -define("spineItem", ["readiumSDK"], (function (global) { - return function () { - var ret, fn; - return ret || global.spineItem; - }; -}(this))); - -// Created by Boris Schneiderman. -// Copyright (c) 2014 Readium Foundation and/or its licensees. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation and/or -// other materials provided with the distribution. -// 3. Neither the name of the organization nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED -// OF THE POSSIBILITY OF SUCH DAMAGE. - -/** - * Wrapper of the spine object received from hosting application - * - * @class ReadiumSDK.Models.Spine - */ - -ReadiumSDK.Models.Spine = function(epubPackage, spineDTO) { - - var self = this; - - /* - * Collection of spine items - * @property items - * @type {Array} - */ - this.items = []; - - /* - * Page progression direction ltr|rtl|default - * @property direction - * @type {string} - */ - this.direction = "ltr"; - - /* - * @property package - * @type {ReadiumSDK.Models.Package} - * - */ - this.package = epubPackage; - - var _handleLinear = false; - - this.handleLinear = function(handleLinear) { - _handleLinear = handleLinear; - }; - - function isValidLinearItem(item) { - return !_handleLinear || item.linear !== "no"; - } - - - this.isValidLinearItem = function(index) { - - if(!isValidIndex(index)) { - return undefined; - } - - return isValidLinearItem(this.item(index)); - }; - - this.prevItem = function(item) { - - return lookForPrevValidItem(item.index - 1); - }; - - function lookForNextValidItem(ix) { - - if(!isValidIndex(ix)) { - return undefined; - } - - var item = self.items[ix]; - - if(isValidLinearItem(item)) { - return item; - } - - return lookForNextValidItem(item.index + 1); - } - - function lookForPrevValidItem(ix) { - - if(!isValidIndex(ix)) { - return undefined; - } - - var item = self.items[ix]; - - if(isValidLinearItem(item)) { - return item; - } - - return lookForPrevValidItem(item.index - 1); - } - - this.nextItem = function(item){ - - return lookForNextValidItem(item.index + 1); - }; - - this.getItemUrl = function(item) { - - return self.package.resolveRelativeUrl(item.href); - - }; - - function isValidIndex(index) { - - return index >= 0 && index < self.items.length; - } - - this.first = function() { - - return lookForNextValidItem(0); - }; - - this.last = function() { - - return lookForPrevValidItem(this.items.length - 1); - }; - - this.isFirstItem = function(item) { - - return self.first() === item; - }; - - this.isLastItem = function(item) { - - return self.last() === item; - }; - - this.item = function(index) { - - if (isValidIndex(index)) - return self.items[index]; - - return undefined; - }; - - this.isRightToLeft = function() { - - return self.direction == "rtl"; - }; - - this.isLeftToRight = function() { - - return !self.isRightToLeft(); - }; - - this.getItemById = function(idref) { - - var length = self.items.length; - - for(var i = 0; i < length; i++) { - if(self.items[i].idref == idref) { - - return self.items[i]; - } - } - - return undefined; - }; - - this.getItemByHref = function(href) { - - var length = self.items.length; - - for(var i = 0; i < length; i++) { - if(self.items[i].href == href) { - - return self.items[i]; - } - } - - return undefined; - }; - - function updateSpineItemsSpread() { - - var len = self.items.length; - - var isFirstPageInSpread = false; - var baseSide = self.isLeftToRight() ? ReadiumSDK.Models.SpineItem.SPREAD_LEFT : ReadiumSDK.Models.SpineItem.SPREAD_RIGHT; - - for(var i = 0; i < len; i++) { - - var spineItem = self.items[i]; - if( !spineItem.page_spread) { - - var spread = spineItem.isRenditionSpreadAllowed() ? (isFirstPageInSpread ? baseSide : ReadiumSDK.Models.SpineItem.alternateSpread(baseSide)) : ReadiumSDK.Models.SpineItem.SPREAD_CENTER; - spineItem.setSpread(spread); - } - - isFirstPageInSpread = !spineItem.isRenditionSpreadAllowed() || spineItem.page_spread != baseSide; - } - } - - if(spineDTO) { - - if(spineDTO.direction) { - this.direction = spineDTO.direction; - } - - var length = spineDTO.items.length; - for(var i = 0; i < length; i++) { - var item = new ReadiumSDK.Models.SpineItem(spineDTO.items[i], i, this); - this.items.push(item); - } - - updateSpineItemsSpread(); - } - -}; +// Created by Boris Schneiderman. +// Copyright (c) 2014 Readium Foundation and/or its licensees. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// 3. Neither the name of the organization nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. + +/** + * Wrapper of the SpineItem object received from the host application + * + * @class ReadiumSDK.Models.SpineItem + * + * @param itemData spine item properties container + * @param {Number} index + * @param {ReadiumSDK.Models.Spine} spine + * + */ +ReadiumSDK.Models.SpineItem = function(itemData, index, spine){ + + var self = this; + + this.idref = itemData.idref; + this.href = itemData.href; + + this.linear = itemData.linear ? itemData.linear.toLowerCase() : itemData.linear; + + this.page_spread = itemData.page_spread; + + this.rendition_viewport = itemData.rendition_viewport; + + this.rendition_spread = itemData.rendition_spread; + + //TODO: unused yet! + this.rendition_orientation = itemData.rendition_orientation; + + this.rendition_layout = itemData.rendition_layout; + + this.rendition_flow = itemData.rendition_flow; + + + + this.media_overlay_id = itemData.media_overlay_id; + + this.media_type = itemData.media_type; + + this.index = index; + this.spine = spine; + + validateSpread(); + + this.setSpread = function(spread) { + this.page_spread = spread; + + validateSpread(); + }; + + this.isRenditionSpreadAllowed = function() { + + var rendition_spread = self.getRenditionSpread(); + return !rendition_spread || rendition_spread != ReadiumSDK.Models.SpineItem.RENDITION_SPREAD_NONE; + }; + + function validateSpread() { + + if(!self.page_spread) { + return; + } + + if( self.page_spread != ReadiumSDK.Models.SpineItem.SPREAD_LEFT && + self.page_spread != ReadiumSDK.Models.SpineItem.SPREAD_RIGHT && + self.page_spread != ReadiumSDK.Models.SpineItem.SPREAD_CENTER ) { + + console.error(self.page_spread + " is not a recognized spread type"); + } + + } + + this.isLeftPage = function() { + return self.page_spread == ReadiumSDK.Models.SpineItem.SPREAD_LEFT; + }; + + this.isRightPage = function() { + return self.page_spread == ReadiumSDK.Models.SpineItem.SPREAD_RIGHT; + }; + + this.isCenterPage = function() { + return self.page_spread == ReadiumSDK.Models.SpineItem.SPREAD_CENTER; + }; + + this.isReflowable = function() { + return !self.isFixedLayout(); + }; + + this.isFixedLayout = function() { + + // cannot use isPropertyValueSetForItemOrPackage() here! + + var isLayoutExplicitlyDefined = self.getRenditionLayout(); + + if(isLayoutExplicitlyDefined) { + + if (self.rendition_layout) + { + if (self.rendition_layout === ReadiumSDK.Models.SpineItem.RENDITION_LAYOUT_PREPAGINATED) return true; + if (self.rendition_layout === ReadiumSDK.Models.SpineItem.RENDITION_LAYOUT_REFLOWABLE) return false; + } + + return self.spine.package.isFixedLayout(); + } + + // if image or svg use fixed layout + return self.media_type.indexOf("image/") >= 0; + + }; + + this.getRenditionFlow = function() { + + if(self.rendition_flow) { + return self.rendition_flow; + } + + return self.spine.package.rendition_flow; + }; + + this.getRenditionViewport = function() { + + if(self.rendition_viewport) { + return self.rendition_viewport; + } + + return self.spine.package.rendition_viewport; + }; + + this.getRenditionSpread = function() { + + if(self.rendition_spread) { + return self.rendition_spread; + } + + return self.spine.package.rendition_spread; + }; + + this.getRenditionOrientation = function() { + + if(self.rendition_orientation) { + return self.rendition_orientation; + } + + return self.spine.package.rendition_orientation; + }; + + this.getRenditionLayout = function() { + + if(self.rendition_layout) { + return self.rendition_layout; + } + + return self.spine.package.rendition_layout; + }; + + function isPropertyValueSetForItemOrPackage(propName, propValue) { + + if(self[propName]) { + return self[propName] === propValue; + } + + if(self.spine.package[propName]) { + return self.spine.package[propName] === propValue; + } + + return false; + } + + this.isFlowScrolledContinuous = function() { + + return isPropertyValueSetForItemOrPackage("rendition_flow", ReadiumSDK.Models.SpineItem.RENDITION_FLOW_SCROLLED_CONTINUOUS); + }; + + this.isFlowScrolledDoc = function() { + + return isPropertyValueSetForItemOrPackage("rendition_flow", ReadiumSDK.Models.SpineItem.RENDITION_FLOW_SCROLLED_DOC); + }; +}; + +ReadiumSDK.Models.SpineItem.RENDITION_LAYOUT_REFLOWABLE = "reflowable"; +ReadiumSDK.Models.SpineItem.RENDITION_LAYOUT_PREPAGINATED = "pre-paginated"; + +ReadiumSDK.Models.SpineItem.RENDITION_ORIENTATION_LANDSCAPE = "landscape"; +ReadiumSDK.Models.SpineItem.RENDITION_ORIENTATION_PORTRAIT = "portrait"; +ReadiumSDK.Models.SpineItem.RENDITION_ORIENTATION_AUTO = "auto"; + +ReadiumSDK.Models.SpineItem.SPREAD_LEFT = "page-spread-left"; +ReadiumSDK.Models.SpineItem.SPREAD_RIGHT = "page-spread-right"; +ReadiumSDK.Models.SpineItem.SPREAD_CENTER = "page-spread-center"; + +ReadiumSDK.Models.SpineItem.RENDITION_SPREAD_NONE = "none"; +ReadiumSDK.Models.SpineItem.RENDITION_SPREAD_LANDSCAPE = "landscape"; +ReadiumSDK.Models.SpineItem.RENDITION_SPREAD_PORTRAIT = "portrait"; +ReadiumSDK.Models.SpineItem.RENDITION_SPREAD_BOTH = "both"; +ReadiumSDK.Models.SpineItem.RENDITION_SPREAD_AUTO = "auto"; + +ReadiumSDK.Models.SpineItem.RENDITION_FLOW_PAGINATED = "paginated"; +ReadiumSDK.Models.SpineItem.RENDITION_FLOW_SCROLLED_CONTINUOUS = "scrolled-continuous"; +ReadiumSDK.Models.SpineItem.RENDITION_FLOW_SCROLLED_DOC = "scrolled-doc"; +ReadiumSDK.Models.SpineItem.RENDITION_FLOW_AUTO = "auto"; + +ReadiumSDK.Models.SpineItem.alternateSpread = function(spread) { + + if(spread === ReadiumSDK.Models.SpineItem.SPREAD_LEFT) { + return ReadiumSDK.Models.SpineItem.SPREAD_RIGHT; + } + + if(spread === ReadiumSDK.Models.SpineItem.SPREAD_RIGHT) { + return ReadiumSDK.Models.SpineItem.SPREAD_LEFT; + } + + return spread; + +}; + -define("spine", ["readiumSDK","spineItem"], (function (global) { +define("spineItem", ["readiumSDK"], (function (global) { return function () { var ret, fn; - return ret || global.spine; + return ret || global.spineItem; }; }(this))); -// LauncherOSX -// -// Created by Boris Schneiderman. -// Modified by Daniel Weck -// Copyright (c) 2014 Readium Foundation and/or its licensees. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation and/or -// other materials provided with the distribution. -// 3. Neither the name of the organization nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED -// OF THE POSSIBILITY OF SUCH DAMAGE. - - -/** - * - * @param parent - * @constructor - */ - -ReadiumSDK.Models.Smil.SmilNode = function(parent) { - - this.parent = parent; - - this.id = ""; - - //root node is a smil model - this.getSmil = function() { - - var node = this; - while(node.parent) { - node = node.parent; - } - - return node; - }; - - this.hasAncestor = function(node) - { - var parent = this.parent; - while(parent) - { - if (parent == node) - { - return true; - } - - parent = parent.parent; - } - - return false; - }; -}; - -ReadiumSDK.Models.Smil.TimeContainerNode = function(parent) { - - this.parent = parent; - - this.children = undefined; - this.index = undefined; - - this.epubtype = ""; - - this.isEscapable = function(userEscapables) - { - if (this.epubtype === "") - { - return false; - } - - var smilModel = this.getSmil(); - if (!smilModel.mo) - { - return false; - } - - var arr = smilModel.mo.escapables; - if (userEscapables.length > 0) - { - arr = userEscapables; - } - - for (var i = 0; i < arr.length; i++) - { - if (this.epubtype.indexOf(arr[i]) >= 0) - { - return true; - } - } - - return false; - }; - - this.isSkippable = function(userSkippables) - { - if (this.epubtype === "") - { - return false; - } - - var smilModel = this.getSmil(); - if (!smilModel.mo) - { - return false; - } - - var arr = smilModel.mo.skippables; - if (userSkippables.length > 0) - { - arr = userSkippables; - } - - for (var i = 0; i < arr.length; i++) - { - if (this.epubtype.indexOf(arr[i]) >= 0) - { - return true; - } - } - - return false; - }; -}; - -ReadiumSDK.Models.Smil.TimeContainerNode.prototype = new ReadiumSDK.Models.Smil.SmilNode(); - -////////////////////////// -//MediaNode - -ReadiumSDK.Models.Smil.MediaNode = function(parent) { - - this.parent = parent; - - this.src = ""; -}; - -ReadiumSDK.Models.Smil.MediaNode.prototype = new ReadiumSDK.Models.Smil.SmilNode(); - -//////////////////////////// -//SeqNode - -ReadiumSDK.Models.Smil.SeqNode = function(parent) { - - this.parent = parent; - - this.children = []; - this.nodeType = "seq"; - this.textref = ""; - - this.durationMilliseconds = function() - { - var smilData = this.getSmil(); - - var total = 0; - - for (var i = 0; i < this.children.length; i++) - { - var container = this.children[i]; - if (container.nodeType === "par") - { - if (!container.audio) - { - continue; - } - if (container.text && (!container.text.manifestItemId || container.text.manifestItemId != smilData.spineItemId)) - { -// console.log(container.text); -// console.log(smilData.spineItemId); - continue; - } - - var clipDur = container.audio.clipDurationMilliseconds(); - total += clipDur; - } - else if (container.nodeType === "seq") - { - total += container.durationMilliseconds(); - } - } - - return total; - }; - - this.clipOffset = function(offset, par) - { - var smilData = this.getSmil(); - - for (var i = 0; i < this.children.length; i++) - { - var container = this.children[i]; - if (container.nodeType === "par") - { - if (container == par) - { - return true; - } - - if (!container.audio) - { - continue; - } - - if (container.text && (!container.text.manifestItemId || container.text.manifestItemId != smilData.spineItemId)) - { - continue; - } - - var clipDur = container.audio.clipDurationMilliseconds(); - offset.offset += clipDur; - } - else if (container.nodeType === "seq") - { - var found = container.clipOffset(offset, par); - if (found) - { - return true; - } - } - } - - return false; - }; - - this.parallelAt = function(timeMilliseconds) - { - var smilData = this.getSmil(); - - var offset = 0; - - for (var i = 0; i < this.children.length; i++) - { - var timeAdjusted = timeMilliseconds - offset; - - var container = this.children[i]; - - if (container.nodeType === "par") - { - if (!container.audio) - { - continue; - } - - if (container.text && (!container.text.manifestItemId || container.text.manifestItemId != smilData.spineItemId)) - { - continue; - } - - var clipDur = container.audio.clipDurationMilliseconds(); - - if (clipDur > 0 && timeAdjusted <= clipDur) - { - return container; - } - - offset += clipDur; - } - else if (container.nodeType === "seq") - { - var para = container.parallelAt(timeAdjusted); - if (para) - { - return para; - } - - offset += container.durationMilliseconds(); - } - } - - return undefined; - }; - - this.nthParallel = function(index, count) - { - for (var i = 0; i < this.children.length; i++) - { - var container = this.children[i]; - - if (container.nodeType === "par") - { - count.count++; - - if (count.count == index) - { - return container; - } - } - else if (container.nodeType === "seq") - { - var para = container.nthParallel(index, count); - if (para) - { - return para; - } - } - } - - return undefined; - }; - -}; - -ReadiumSDK.Models.Smil.SeqNode.prototype = new ReadiumSDK.Models.Smil.TimeContainerNode(); - -////////////////////////// -//ParNode - -ReadiumSDK.Models.Smil.ParNode = function(parent) { - - this.parent = parent; - - this.children = []; - this.nodeType = "par"; - this.text = undefined; - this.audio = undefined; - this.element = undefined; - - - this.getFirstSeqAncestorWithEpubType = function(epubtype, includeSelf) { - if (!epubtype) return undefined; - - var parent = includeSelf ? this : this.parent; - while (parent) - { - if (parent.epubtype && parent.epubtype.indexOf(epubtype) >= 0) - { - return parent; // assert(parent.nodeType === "seq") - } - - parent = parent.parent; - } - - return undefined; - }; -}; - -ReadiumSDK.Models.Smil.ParNode.prototype = new ReadiumSDK.Models.Smil.TimeContainerNode(); - -////////////////////////// -//TextNode - -ReadiumSDK.Models.Smil.TextNode = function(parent) { - - this.parent = parent; - - this.nodeType = "text"; - this.srcFile = ""; - this.srcFragmentId = ""; - - - this.manifestItemId = undefined; - this.updateMediaManifestItemId = function() - { - var smilData = this.getSmil(); - - if (!smilData.href || !smilData.href.length) - { - return; // Blank MO page placeholder, no real SMIL - } - - // var srcParts = item.src.split('#'); -// item.srcFile = srcParts[0]; -// item.srcFragmentId = (srcParts.length === 2) ? srcParts[1] : ""; - - var src = this.srcFile ? this.srcFile : this.src; -// console.log("src: " + src); -// console.log("smilData.href: " + smilData.href); - var ref = ReadiumSDK.Helpers.ResolveContentRef(src, smilData.href); -//console.log("ref: " + ref); - var full = smilData.mo.package.resolveRelativeUrlMO(ref); -// console.log("full: " + full); -// console.log("---"); - for (var j = 0; j < smilData.mo.package.spine.items.length; j++) - { - var item = smilData.mo.package.spine.items[j]; -//console.log("item.href: " + item.href); - var url = smilData.mo.package.resolveRelativeUrl(item.href); -//console.log("url: " + url); - if (url === full) - { -//console.error("FOUND: " + item.idref); - this.manifestItemId = item.idref; - return; - } - } - - console.error("Cannot set the Media ManifestItemId? " + this.src + " && " + smilData.href); - -// throw "BREAK"; - }; - -}; - -ReadiumSDK.Models.Smil.TextNode.prototype = new ReadiumSDK.Models.Smil.MediaNode(); - -/////////////////////////// -//AudioNode - -ReadiumSDK.Models.Smil.AudioNode = function(parent) { - - this.parent = parent; - - this.nodeType = "audio"; - - this.clipBegin = 0; - - this.MAX = 1234567890.1; //Number.MAX_VALUE - 0.1; //Infinity; - this.clipEnd = this.MAX; - - - this.clipDurationMilliseconds = function() - { - var _clipBeginMilliseconds = this.clipBegin * 1000; - var _clipEndMilliseconds = this.clipEnd * 1000; - - if (this.clipEnd >= this.MAX || _clipEndMilliseconds <= _clipBeginMilliseconds) - { - return 0; - } - - return _clipEndMilliseconds - _clipBeginMilliseconds; - }; -}; - -ReadiumSDK.Models.Smil.AudioNode.prototype = new ReadiumSDK.Models.Smil.MediaNode(); - -////////////////////////////// -//SmilModel - -ReadiumSDK.Models.SmilModel = function() { - - this.parent = undefined; - - - - this.children = []; //collection of seq or par smil nodes - this.id = undefined; //manifest item id - this.href = undefined; //href of the .smil source file - this.duration = undefined; - this.mo = undefined; - - this.parallelAt = function(timeMilliseconds) - { - return this.children[0].parallelAt(timeMilliseconds); - }; - - this.nthParallel = function(index) - { - var count = {count: -1}; - return this.children[0].nthParallel(index, count); - }; - - this.clipOffset = function(par) - { - var offset = {offset: 0}; - if (this.children[0].clipOffset(offset, par)) - { - return offset.offset; - } - - return 0; - }; - - this.durationMilliseconds_Calculated = function() - { - return this.children[0].durationMilliseconds(); - }; - - - var _epubtypeSyncs = []; - // - // this.clearSyncs = function() - // { - // _epubtypeSyncs = []; - // }; - - this.hasSync = function(epubtype) - { - for (var i = 0; i < _epubtypeSyncs.length; i++) - { - if (_epubtypeSyncs[i] === epubtype) - { -//console.debug("hasSync OK: ["+epubtype+"]"); - return true; - } - } - -//console.debug("hasSync??: ["+epubtype+"] " + _epubtypeSyncs); - return false; - }; - - this.addSync = function(epubtypes) - { - if (!epubtypes) return; - -//console.debug("addSyncs: "+epubtypes); - - var parts = epubtypes.split(' '); - for (var i = 0; i < parts.length; i++) - { - var epubtype = parts[i].trim(); - - if (epubtype.length > 0 && !this.hasSync(epubtype)) - { - _epubtypeSyncs.push(epubtype); - -//console.debug("addSync: "+epubtype); - } - } - }; - -}; - -ReadiumSDK.Models.SmilModel.fromSmilDTO = function(smilDTO, mo) { - - if (mo.DEBUG) - { - console.debug("Media Overlay DTO import..."); - } - - var indent = 0; - var getIndent = function() - { - var str = ""; - for (var i = 0; i < indent; i++) - { - str += " "; - } - return str; - } - - var smilModel = new ReadiumSDK.Models.SmilModel(); - smilModel.id = smilDTO.id; - smilModel.spineItemId = smilDTO.spineItemId; - smilModel.href = smilDTO.href; - - smilModel.smilVersion = smilDTO.smilVersion; - - smilModel.duration = smilDTO.duration; - if (smilModel.duration && smilModel.duration.length && smilModel.duration.length > 0) - { - console.error("SMIL duration is string, parsing float... (" + smilModel.duration + ")"); - smilModel.duration = parseFloat(smilModel.duration); - } - - smilModel.mo = mo; //ReadiumSDK.Models.MediaOverlay - - if (smilModel.mo.DEBUG) - { - console.log("JS MO smilVersion=" + smilModel.smilVersion); - console.log("JS MO id=" + smilModel.id); - console.log("JS MO spineItemId=" + smilModel.spineItemId); - console.log("JS MO href=" + smilModel.href); - console.log("JS MO duration=" + smilModel.duration); - } - - var safeCopyProperty = function(property, from, to, isRequired) { - - if((property in from)) - { // && from[property] !== "" - - if( !(property in to) ) { - console.debug("property " + property + " not declared in smil node " + to.nodeType); - } - - to[property] = from[property]; - - if (smilModel.mo.DEBUG) - { - console.log(getIndent() + "JS MO: [" + property + "=" + to[property] + "]"); - } - } - else if(isRequired) { - console.log("Required property " + property + " not found in smil node " + from.nodeType); - } - }; - - var createNodeFromDTO = function(nodeDTO, parent) { - - var node; - - if(nodeDTO.nodeType == "seq") { - - if (smilModel.mo.DEBUG) - { - console.log(getIndent() + "JS MO seq"); - } - - node = new ReadiumSDK.Models.Smil.SeqNode(parent); - - safeCopyProperty("textref", nodeDTO, node, ((parent && parent.parent) ? true : false)); - safeCopyProperty("id", nodeDTO, node); - safeCopyProperty("epubtype", nodeDTO, node); - - if (node.epubtype) - { - node.getSmil().addSync(node.epubtype); - } - - indent++; - copyChildren(nodeDTO, node); - indent--; - } - else if (nodeDTO.nodeType == "par") { - - if (smilModel.mo.DEBUG) - { - console.log(getIndent() + "JS MO par"); - } - - node = new ReadiumSDK.Models.Smil.ParNode(parent); - - safeCopyProperty("id", nodeDTO, node); - safeCopyProperty("epubtype", nodeDTO, node); - - if (node.epubtype) - { - node.getSmil().addSync(node.epubtype); - } - - indent++; - copyChildren(nodeDTO, node); - indent--; - - for(var i = 0, count = node.children.length; i < count; i++) { - var child = node.children[i]; - - if(child.nodeType == "text") { - node.text = child; - } - else if(child.nodeType == "audio") { - node.audio = child; - } - else { - console.error("Unexpected smil node type: " + child.nodeType); - } - } - -//////////////// -var forceTTS = false; // for testing only! -//////////////// - - if (forceTTS || !node.audio) - { - // synthetic speech (playback using TTS engine), or embedded media, or blank page - var fakeAudio = new ReadiumSDK.Models.Smil.AudioNode(node); - - fakeAudio.clipBegin = 0; - fakeAudio.clipEnd = fakeAudio.MAX; - fakeAudio.src = undefined; - - node.audio = fakeAudio; - } - } - else if (nodeDTO.nodeType == "text") { - - if (smilModel.mo.DEBUG) - { - console.log(getIndent() + "JS MO text"); - } - - node = new ReadiumSDK.Models.Smil.TextNode(parent); - - safeCopyProperty("src", nodeDTO, node, true); - safeCopyProperty("srcFile", nodeDTO, node, true); - safeCopyProperty("srcFragmentId", nodeDTO, node, false); - safeCopyProperty("id", nodeDTO, node); - - node.updateMediaManifestItemId(); - } - else if (nodeDTO.nodeType == "audio") { - - if (smilModel.mo.DEBUG) - { - console.log(getIndent() + "JS MO audio"); - } - - node = new ReadiumSDK.Models.Smil.AudioNode(parent); - - safeCopyProperty("src", nodeDTO, node, true); - safeCopyProperty("id", nodeDTO, node); - - safeCopyProperty("clipBegin", nodeDTO, node); - if (node.clipBegin && node.clipBegin.length && node.clipBegin.length > 0) - { - console.error("SMIL clipBegin is string, parsing float... (" + node.clipBegin + ")"); - node.clipBegin = parseFloat(node.clipBegin); - } - if (node.clipBegin < 0) - { - if (smilModel.mo.DEBUG) - { - console.log(getIndent() + "JS MO clipBegin adjusted to ZERO"); - } - node.clipBegin = 0; - } - - safeCopyProperty("clipEnd", nodeDTO, node); - if (node.clipEnd && node.clipEnd.length && node.clipEnd.length > 0) - { - console.error("SMIL clipEnd is string, parsing float... (" + node.clipEnd + ")"); - node.clipEnd = parseFloat(node.clipEnd); - } - if (node.clipEnd <= node.clipBegin) - { - if (smilModel.mo.DEBUG) - { - console.log(getIndent() + "JS MO clipEnd adjusted to MAX"); - } - node.clipEnd = node.MAX; - } - - //node.updateMediaManifestItemId(); ONLY XHTML SPINE ITEMS - } - else { - console.error("Unexpected smil node type: " + nodeDTO.nodeType); - return undefined; - } - - return node; - - }; - - var copyChildren = function(from, to) { - - var count = from.children.length; - - for(var i = 0; i < count; i++) { - var node = createNodeFromDTO(from.children[i], to); - node.index = i; - to.children.push(node); - } - - }; - - copyChildren(smilDTO, smilModel); - - return smilModel; - -}; - -define("smilModel", ["readiumSDK"], (function (global) { - return function () { - var ret, fn; - return ret || global.smilModel; - }; -}(this))); - -// LauncherOSX -// -// Created by Boris Schneiderman. -// Modified by Daniel Weck -// Copyright (c) 2014 Readium Foundation and/or its licensees. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation and/or -// other materials provided with the distribution. -// 3. Neither the name of the organization nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED -// OF THE POSSIBILITY OF SUCH DAMAGE. - -/** - * - * @param package - * @constructor - */ -ReadiumSDK.Models.MediaOverlay = function(package) { - - this.package = package; - - - this.parallelAt = function(timeMilliseconds) - { - var offset = 0; - - for (var i = 0; i < this.smil_models.length; i++) - { - var smilData = this.smil_models[i]; - - var timeAdjusted = timeMilliseconds - offset; - - var para = smilData.parallelAt(timeAdjusted); - if (para) - { - return para; - } - - offset += smilData.durationMilliseconds_Calculated(); - } - - return undefined; - }; - - this.percentToPosition = function(percent, smilData, par, milliseconds) - { - if (percent < 0.0 || percent > 100.0) - { - percent = 0.0; - } - - var total = this.durationMilliseconds_Calculated(); - - var timeMs = total * (percent / 100.0); - - par.par = this.parallelAt(timeMs); - if (!par.par) - { - return; - } - - var smilDataPar = par.par.getSmil(); - if (!smilDataPar) - { - return; - } - - var smilDataOffset = 0; - - for (var i = 0; i < this.smil_models.length; i++) - { - smilData.smilData = this.smil_models[i]; - if (smilData.smilData == smilDataPar) - { - break; - } - smilDataOffset += smilData.smilData.durationMilliseconds_Calculated(); - } - - milliseconds.milliseconds = timeMs - (smilDataOffset + smilData.smilData.clipOffset(par.par)); - }; - - this.durationMilliseconds_Calculated = function() - { - var total = 0; - - for (var i = 0; i < this.smil_models.length; i++) - { - var smilData = this.smil_models[i]; - - total += smilData.durationMilliseconds_Calculated(); - } - - return total; - }; - - this.smilAt = function(smilIndex) - { - if (smilIndex < 0 || smilIndex >= this.smil_models.length) - { - return undefined; - } - - return this.smil_models[smilIndex]; - } - - this.positionToPercent = function(smilIndex, parIndex, milliseconds) - { -// console.log(">>>>>>>>>>"); -// console.log(milliseconds); -// console.log(smilIndex); -// console.log(parIndex); -// console.log("-------"); - - if (smilIndex >= this.smil_models.length) - { - return -1.0; - } - - var smilDataOffset = 0; - for (var i = 0; i < smilIndex; i++) - { - var sd = this.smil_models[i]; - smilDataOffset += sd.durationMilliseconds_Calculated(); - } - -//console.log(smilDataOffset); - - var smilData = this.smil_models[smilIndex]; - - var par = smilData.nthParallel(parIndex); - if (!par) - { - return -1.0; - } - - var offset = smilDataOffset + smilData.clipOffset(par) + milliseconds; - -//console.log(offset); - - var total = this.durationMilliseconds_Calculated(); - -///console.log(total); - - var percent = (offset / total) * 100; - -//console.log("<<<<<<<<<<< " + percent); - - return percent; - }; - - this.smil_models = []; - - this.skippables = []; - this.escapables = []; - - this.duration = undefined; - this.narrator = undefined; - - - this.activeClass = undefined; - this.playbackActiveClass = undefined; - - this.DEBUG = false; - - - this.getSmilBySpineItem = function (spineItem) { - if (!spineItem) return undefined; - - for(var i = 0, count = this.smil_models.length; i < count; i++) - { - var smil = this.smil_models[i]; - if(smil.spineItemId === spineItem.idref) { - if (spineItem.media_overlay_id !== smil.id) - { - console.error("SMIL INCORRECT ID?? " + spineItem.media_overlay_id + " /// " + smil.id); - } - return smil; - } - } - - return undefined; - }; - - /* - this.getSmilById = function (id) { - - for(var i = 0, count = this.smil_models.length; i < count; i++) { - - var smil = this.smil_models[i]; - if(smil.id === id) { - return smil; - } - } - - return undefined; - }; - */ - - this.getNextSmil = function(smil) { - - var index = this.smil_models.indexOf(smil); - if(index == -1 || index == this.smil_models.length - 1) { - return undefined; - } - - return this.smil_models[index + 1]; - } - - this.getPreviousSmil = function(smil) { - - var index = this.smil_models.indexOf(smil); - if(index == -1 || index == 0) { - return undefined; - } - - return this.smil_models[index - 1]; - } -}; - -ReadiumSDK.Models.MediaOverlay.fromDTO = function(moDTO, package) { - - var mo = new ReadiumSDK.Models.MediaOverlay(package); - - if(!moDTO) { - console.debug("No Media Overlay."); - return mo; - } - - console.debug("Media Overlay INIT..."); - - // if (mo.DEBUG) - // console.debug(JSON.stringify(moDTO)); - - mo.duration = moDTO.duration; - if (mo.duration && mo.duration.length && mo.duration.length > 0) - { - console.error("SMIL total duration is string, parsing float... (" + mo.duration + ")"); - mo.duration = parseFloat(mo.duration); - } - if (mo.DEBUG) - console.debug("Media Overlay Duration (TOTAL): " + mo.duration); - - mo.narrator = moDTO.narrator; - if (mo.DEBUG) - console.debug("Media Overlay Narrator: " + mo.narrator); - - mo.activeClass = moDTO.activeClass; - if (mo.DEBUG) - console.debug("Media Overlay Active-Class: " + mo.activeClass); - - mo.playbackActiveClass = moDTO.playbackActiveClass; - if (mo.DEBUG) - console.debug("Media Overlay Playback-Active-Class: " + mo.playbackActiveClass); - - var count = moDTO.smil_models.length; - if (mo.DEBUG) - console.debug("Media Overlay SMIL count: " + count); - - for(var i = 0; i < count; i++) { - var smilModel = ReadiumSDK.Models.SmilModel.fromSmilDTO(moDTO.smil_models[i], mo); - mo.smil_models.push(smilModel); - - if (mo.DEBUG) - console.debug("Media Overlay Duration (SPINE ITEM): " + smilModel.duration); - } - - count = moDTO.skippables.length; - if (mo.DEBUG) - console.debug("Media Overlay SKIPPABLES count: " + count); - - for(var i = 0; i < count; i++) { - mo.skippables.push(moDTO.skippables[i]); - - //if (mo.DEBUG) - // console.debug("Media Overlay SKIPPABLE: " + mo.skippables[i]); - } - - count = moDTO.escapables.length; - if (mo.DEBUG) - console.debug("Media Overlay ESCAPABLES count: " + count); - - for(var i = 0; i < count; i++) { - mo.escapables.push(moDTO.escapables[i]); - - //if (mo.DEBUG) - // console.debug("Media Overlay ESCAPABLE: " + mo.escapables[i]); - } - - return mo; -}; - - +// Created by Boris Schneiderman. +// Copyright (c) 2014 Readium Foundation and/or its licensees. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without modification, +// are permitted provided that the following conditions are met: +// 1. Redistributions of source code must retain the above copyright notice, this +// list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright notice, +// this list of conditions and the following disclaimer in the documentation and/or +// other materials provided with the distribution. +// 3. Neither the name of the organization nor the names of its contributors may be +// used to endorse or promote products derived from this software without specific +// prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. + +/** + * Wrapper of the spine object received from hosting application + * + * @class ReadiumSDK.Models.Spine + */ + +ReadiumSDK.Models.Spine = function(epubPackage, spineDTO) { + + var self = this; + + /* + * Collection of spine items + * @property items + * @type {Array} + */ + this.items = []; + + /* + * Page progression direction ltr|rtl|default + * @property direction + * @type {string} + */ + this.direction = "ltr"; + + /* + * @property package + * @type {ReadiumSDK.Models.Package} + * + */ + this.package = epubPackage; + + var _handleLinear = false; + + this.handleLinear = function(handleLinear) { + _handleLinear = handleLinear; + }; + + function isValidLinearItem(item) { + return !_handleLinear || item.linear !== "no"; + } + + + this.isValidLinearItem = function(index) { + + if(!isValidIndex(index)) { + return undefined; + } + + return isValidLinearItem(this.item(index)); + }; + + this.prevItem = function(item) { + + return lookForPrevValidItem(item.index - 1); + }; + + function lookForNextValidItem(ix) { + + if(!isValidIndex(ix)) { + return undefined; + } + + var item = self.items[ix]; + + if(isValidLinearItem(item)) { + return item; + } + + return lookForNextValidItem(item.index + 1); + } + + function lookForPrevValidItem(ix) { + + if(!isValidIndex(ix)) { + return undefined; + } + + var item = self.items[ix]; + + if(isValidLinearItem(item)) { + return item; + } + + return lookForPrevValidItem(item.index - 1); + } + + this.nextItem = function(item){ + + return lookForNextValidItem(item.index + 1); + }; + + this.getItemUrl = function(item) { + + return self.package.resolveRelativeUrl(item.href); + + }; + + function isValidIndex(index) { + + return index >= 0 && index < self.items.length; + } + + this.first = function() { + + return lookForNextValidItem(0); + }; + + this.last = function() { + + return lookForPrevValidItem(this.items.length - 1); + }; + + this.isFirstItem = function(item) { + + return self.first() === item; + }; + + this.isLastItem = function(item) { + + return self.last() === item; + }; + + this.item = function(index) { + + if (isValidIndex(index)) + return self.items[index]; + + return undefined; + }; + + this.isRightToLeft = function() { + + return self.direction == "rtl"; + }; + + this.isLeftToRight = function() { + + return !self.isRightToLeft(); + }; + + this.getItemById = function(idref) { + + var length = self.items.length; + + for(var i = 0; i < length; i++) { + if(self.items[i].idref == idref) { + + return self.items[i]; + } + } + + return undefined; + }; + + this.getItemByHref = function(href) { + + var length = self.items.length; + + for(var i = 0; i < length; i++) { + if(self.items[i].href == href) { + + return self.items[i]; + } + } + + return undefined; + }; + + function updateSpineItemsSpread() { + + var len = self.items.length; + + var isFirstPageInSpread = false; + var baseSide = self.isLeftToRight() ? ReadiumSDK.Models.SpineItem.SPREAD_LEFT : ReadiumSDK.Models.SpineItem.SPREAD_RIGHT; + + for(var i = 0; i < len; i++) { + + var spineItem = self.items[i]; + if( !spineItem.page_spread) { + + var spread = spineItem.isRenditionSpreadAllowed() ? (isFirstPageInSpread ? baseSide : ReadiumSDK.Models.SpineItem.alternateSpread(baseSide)) : ReadiumSDK.Models.SpineItem.SPREAD_CENTER; + spineItem.setSpread(spread); + } + + isFirstPageInSpread = !spineItem.isRenditionSpreadAllowed() || spineItem.page_spread != baseSide; + } + } + + if(spineDTO) { + + if(spineDTO.direction) { + this.direction = spineDTO.direction; + } + + var length = spineDTO.items.length; + for(var i = 0; i < length; i++) { + var item = new ReadiumSDK.Models.SpineItem(spineDTO.items[i], i, this); + this.items.push(item); + } + + updateSpineItemsSpread(); + } + +}; -define("mediaOverlay", ["readiumSDK","smilModel"], (function (global) { +define("spine", ["readiumSDK","spineItem"], (function (global) { return function () { var ret, fn; - return ret || global.mediaOverlay; + return ret || global.spine; }; }(this))); -// Created by Boris Schneiderman. -// Copyright (c) 2014 Readium Foundation and/or its licensees. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation and/or -// other materials provided with the distribution. -// 3. Neither the name of the organization nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED -// OF THE POSSIBILITY OF SUCH DAMAGE. - -/** - * - * @class ReadiumSDK.Models.Package - * @constructor - */ - -ReadiumSDK.Models.Package = function(packageData){ - - var self = this; - - this.spine = undefined; - - this.rootUrl = undefined; - this.rootUrlMO = undefined; - - this.media_overlay = undefined; - - this.rendition_viewport = undefined; - - this.rendition_flow = undefined; - - this.rendition_layout = undefined; - - //TODO: unused yet! - this.rendition_spread = undefined; - - //TODO: unused yet! - this.rendition_orientation = undefined; - - this.resolveRelativeUrlMO = function(relativeUrl) { - - if(self.rootUrlMO && self.rootUrlMO.length > 0) { - - if(ReadiumSDK.Helpers.EndsWith(self.rootUrlMO, "/")){ - return self.rootUrlMO + relativeUrl; - } - else { - return self.rootUrlMO + "/" + relativeUrl; - } - } - - return self.resolveRelativeUrl(relativeUrl); - }; - - this.resolveRelativeUrl = function(relativeUrl) { - - if(self.rootUrl) { - - if(ReadiumSDK.Helpers.EndsWith(self.rootUrl, "/")){ - return self.rootUrl + relativeUrl; - } - else { - return self.rootUrl + "/" + relativeUrl; - } - } - - return relativeUrl; - }; - - this.isFixedLayout = function() { - return self.rendition_layout === ReadiumSDK.Models.SpineItem.RENDITION_LAYOUT_PREPAGINATED; - }; - - this.isReflowable = function() { - return !self.isFixedLayout(); - }; - - - if(packageData) { - - this.rootUrl = packageData.rootUrl; - this.rootUrlMO = packageData.rootUrlMO; - - this.rendition_viewport = packageData.rendition_viewport; - - this.rendition_layout = packageData.rendition_layout; - - this.rendition_flow = packageData.rendition_flow; - this.rendition_orientation = packageData.rendition_orientation; - this.rendition_spread = packageData.rendition_spread; - - this.spine = new ReadiumSDK.Models.Spine(this, packageData.spine); - - this.media_overlay = ReadiumSDK.Models.MediaOverlay.fromDTO(packageData.media_overlay, this); - } -}; - -define("package", ["readiumSDK","spine","mediaOverlay"], (function (global) { - return function () { - var ret, fn; - return ret || global.package; - }; -}(this))); - -// LauncherOSX -// -// Created by Boris Schneiderman. -// Modified by Daniel Weck, Andrey Kavarma -// Copyright (c) 2014 Readium Foundation and/or its licensees. All rights reserved. -// -// Redistribution and use in source and binary forms, with or without modification, -// are permitted provided that the following conditions are met: -// 1. Redistributions of source code must retain the above copyright notice, this -// list of conditions and the following disclaimer. -// 2. Redistributions in binary form must reproduce the above copyright notice, -// this list of conditions and the following disclaimer in the documentation and/or -// other materials provided with the distribution. -// 3. Neither the name of the organization nor the names of its contributors may be -// used to endorse or promote products derived from this software without specific -// prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, -// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF -// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE -// OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED -// OF THE POSSIBILITY OF SUCH DAMAGE. - - -(function(){ - - var _iOS = navigator.userAgent.match(/(iPad|iPhone|iPod)/g) ? true : false; - var _Android = navigator.userAgent.toLowerCase().indexOf('android') > -1; - var _isMobile = _iOS || _Android; - - //var _isReadiumJS = typeof window.requirejs !== "undefined"; - - var DEBUG = false; - - var _audioElement = new Audio(); - - if (DEBUG) - { - _audioElement.addEventListener("load", function() - { - console.debug("0) load"); - } - ); - - _audioElement.addEventListener("loadstart", function() - { - console.debug("1) loadstart"); - } - ); - - _audioElement.addEventListener("durationchange", function() - { - console.debug("2) durationchange"); - } - ); - - _audioElement.addEventListener("loadedmetadata", function() - { - console.debug("3) loadedmetadata"); - } - ); - - _audioElement.addEventListener("loadeddata", function() - { - console.debug("4) loadeddata"); - } - ); - - _audioElement.addEventListener("progress", function() - { - console.debug("5) progress"); - } - ); - - _audioElement.addEventListener("canplay", function() - { - console.debug("6) canplay"); - } - ); - - _audioElement.addEventListener("canplaythrough", function() - { - console.debug("7) canplaythrough"); - } - ); - - _audioElement.addEventListener("play", function() - { - console.debug("8) play"); - } - ); - - _audioElement.addEventListener("pause", function() - { - console.debug("9) pause"); - } - ); - - _audioElement.addEventListener("ended", function() - { - console.debug("10) ended"); - } - ); - - _audioElement.addEventListener("seeked", function() - { - console.debug("X) seeked"); - } - ); - - _audioElement.addEventListener("timeupdate", function() - { - console.debug("Y) timeupdate"); - } - ); - - _audioElement.addEventListener("seeking", function() - { - console.debug("Z) seeking"); - } - ); - } - - /** - * - * @param onStatusChanged - * @param onPositionChanged - * @param onAudioEnded - * @param onAudioPlay - * @param onAudioPause - * @constructor - */ - ReadiumSDK.Views.AudioPlayer = function(onStatusChanged, onPositionChanged, onAudioEnded, onAudioPlay, onAudioPause) - { - var self = this; - - //_audioElement.setAttribute("preload", "auto"); - - var _currentEpubSrc = undefined; - - var _currentSmilSrc = undefined; - this.currentSmilSrc = function() { - return _currentSmilSrc; - }; - - var _rate = 1.0; - this.setRate = function(rate) - { - _rate = rate; - if (_rate < 0.5) - { - _rate = 0.5; - } - if (_rate > 4.0) - { - _rate = 4.0; - } - - _audioElement.playbackRate = _rate; - } - self.setRate(_rate); - this.getRate = function() - { - return _rate; - } - - - var _volume = 100.0; - this.setVolume = function(volume) - { - _volume = volume; - if (_volume < 0.0) - { - _volume = 0.0; - } - if (_volume > 1.0) - { - _volume = 1.0; - } - _audioElement.volume = _volume; - } - self.setVolume(_volume); - this.getVolume = function() - { - return _volume; - } - - this.play = function() - { - if (DEBUG) - { - console.error("this.play()"); - } - - if(!_currentEpubSrc) - { - return false; - } - - startTimer(); - - self.setVolume(_volume); - self.setRate(_rate); - - _audioElement.play(); - - return true; - }; - - this.pause = function() - { - if (DEBUG) - { - console.error("this.pause()"); - } - - stopTimer(); - - _audioElement.pause(); - }; - - _audioElement.addEventListener('play', onPlay, false); - _audioElement.addEventListener('pause', onPause, false); - _audioElement.addEventListener('ended', onEnded, false); - - function onPlay() - { - onStatusChanged({isPlaying: true}); - onAudioPlay(); - } - - function onPause() - { - onAudioPause(); - onStatusChanged({isPlaying: false}); - } - - function onEnded() - { - if (_audioElement.moSeeking) - { - if (DEBUG) - { - console.debug("onEnded() skipped (still seeking...)"); - } - - return; - } - - stopTimer(); - - onAudioEnded(); - onStatusChanged({isPlaying: false}); - } - - var _intervalTimerSkips = 0; - - var _intervalTimer = undefined; - function startTimer() - { - if(_intervalTimer) - { - return; - } - - _intervalTimer = setInterval( - function() - { - if (_audioElement.moSeeking) - { - if (DEBUG) - { -//console.debug("interval timer skipped (still seeking...)"); - } - - _intervalTimerSkips++; - if (_intervalTimerSkips > 1000) - { - _intervalTimerSkips = 0; - stopTimer(); - } - return; - } - - var currentTime = undefined; - try - { - currentTime = _audioElement.currentTime; - } - catch (ex) - { - console.error(ex.message); - } - - // if (DEBUG) - // { - // console.debug("currentTime: " + currentTime); - // } - - if (currentTime) - { - onPositionChanged(currentTime, 1); - } - }, 20); - } - - function stopTimer() - { - if (_intervalTimer) - { - clearInterval(_intervalTimer); - } - _intervalTimer = undefined; - } - - this.isPlaying = function() - { - return _intervalTimer !== undefined; - }; - - this.reset = function() - { - if (DEBUG) - { - console.error("this.reset()"); - } - - this.pause(); - - _audioElement.moSeeking = undefined; - - _currentSmilSrc = undefined; - _currentEpubSrc = undefined; - - setTimeout(function() - { - _audioElement.setAttribute("src", ""); - }, 1); - }; - - - _audioElement.addEventListener("loadstart", function() - { - _touchInited = true; - } - ); - var _touchInited = false; - this.touchInit = function() - { - if (!_iOS) - { - return false; - } - - if (_touchInited) - { - return false; - } - - _touchInited = true; - - _audioElement.setAttribute("src", "touch/init/html5/audio.mp3"); - _audioElement.load(); - - return true; - } - - var _playId = 0; - - var _seekQueuing = 0; - - this.playFile = function(smilSrc, epubSrc, seekBegin) //element - { - _playId++; - if (_playId > 99999) - { - _playId = 0; - } - - var playId = _playId; - - if (_audioElement.moSeeking) - { - _seekQueuing++; - if (_seekQueuing > MAX_SEEK_RETRIES) - { - _seekQueuing = 0; - return; - } - - if (DEBUG) - { - console.debug("this.playFile(" + epubSrc + ")" + " @" + seekBegin + " (POSTPONE, SEEKING...)"); - } - - setTimeout(function() - { - self.playFile(smilSrc, epubSrc, seekBegin); - }, 20); - - return; - } - - _audioElement.moSeeking = {}; - - if (DEBUG) - { - console.debug("this.playFile(" + epubSrc + ")" + " @" + seekBegin + " #" + playId); - } - - var audioNeedsNewSrc = !_currentEpubSrc || _currentEpubSrc !== epubSrc; - - if (!audioNeedsNewSrc) - { - if (DEBUG) - { - console.debug("this.playFile() SAME SRC"); - } - - this.pause(); - - _currentSmilSrc = smilSrc; - _currentEpubSrc = epubSrc; - - playSeekCurrentTime(seekBegin, playId, false); - - return; - } - - if (DEBUG) - { - console.debug("this.playFile() NEW SRC"); - console.debug("_currentEpubSrc: " + _currentEpubSrc); - console.debug("epubSrc: " + epubSrc); - } - - this.reset(); - _audioElement.moSeeking = {}; - - _currentSmilSrc = smilSrc; - _currentEpubSrc = epubSrc; - - //element.parentNode.insertBefore(_audioElement, element); //element.parentNode.childNodes[0]); - - if (!_Android) - { - _audioElement.addEventListener('play', onPlayToForcePreload, false); - } - - $(_audioElement).on(_readyEvent, {seekBegin: seekBegin, playId: playId}, onReadyToSeek); - - setTimeout(function() - { - _audioElement.setAttribute("src", _currentEpubSrc); - // _audioElement.src = _currentEpubSrc; - // $(_audioElement).attr("src", _currentEpubSrc); - - // if (_Android) - // { - // _audioElement.addEventListener('loadstart', onReadyToPlayToForcePreload, false); - // } - - _audioElement.load(); - - if (!_Android) - { - playToForcePreload(); - } - }, 1); - }; - - // var onReadyToPlayToForcePreload = function () - // { - // _audioElement.removeEventListener('loadstart', onReadyToPlayToForcePreload, false); - // - // if (DEBUG) - // { - // console.debug("onReadyToPlayToForcePreload"); - // } - // - // playToForcePreload(); - // }; - - var playToForcePreload = function() - { - if (DEBUG) - { - console.debug("playToForcePreload"); - } - - //_audioElement.volume = 0; - //_audioElement.play(); - var vol = _volume; - _volume = 0; - self.play(); - _volume = vol; - }; - - var onPlayToForcePreload = function () - { - _audioElement.removeEventListener('play', onPlayToForcePreload, false); - - if (DEBUG) - { - console.debug("onPlayToForcePreload"); - } - _audioElement.pause(); // note: interval timer continues (immediately follows self.play()) - }; - - var _readyEvent = _Android ? "canplaythrough" : "canplay"; - function onReadyToSeek_(event) - { - if (DEBUG) - { - console.debug("onReadyToSeek #" + event.data.playId); - } - playSeekCurrentTime(event.data.seekBegin, event.data.playId, true); - } - function onReadyToSeek(event) - { - $(_audioElement).off(_readyEvent, onReadyToSeek); - - if (!_Android) - { - onReadyToSeek_(event); - } - else - { - if (DEBUG) - { - console.debug("onReadyToSeek ANDROID ... waiting a bit ... #" + event.data.playId); - } - - //self.play(); - playToForcePreload(); - - setTimeout(function() { - onReadyToSeek_(event); - }, 1000); - } - } - - function playSeekCurrentTime(newCurrentTime, playId, isNewSrc) - { - if (DEBUG) - { - console.debug("playSeekCurrentTime() #" + playId); - } - - if (newCurrentTime == 0) - { - newCurrentTime = 0.01; - } - - if(Math.abs(newCurrentTime - _audioElement.currentTime) < 0.3) - { - if (DEBUG) - { - console.debug("playSeekCurrentTime() CONTINUE"); - } - - _audioElement.moSeeking = undefined; - self.play(); - return; - } - - var ev = isNewSrc ? _seekedEvent1 : _seekedEvent2; - - if (DEBUG) - { - console.debug("playSeekCurrentTime() NEED SEEK, EV: " + ev); - } - - self.pause(); - - $(_audioElement).on(ev, {newCurrentTime: newCurrentTime, playId: playId, isNewSrc: isNewSrc}, onSeeked); - - try - { - _audioElement.currentTime = newCurrentTime; - } - catch (ex) - { - console.error(ex.message); - - setTimeout(function() - { - try - { - _audioElement.currentTime = newCurrentTime; - } - catch (ex) - { - console.error(ex.message); - } - }, 5); - } - } - - var MAX_SEEK_RETRIES = 10; - var _seekedEvent1 = _iOS ? "canplaythrough" : "seeked"; //"progress" - var _seekedEvent2 = _iOS ? "timeupdate" : "seeked"; - function onSeeked(event) - { - var ev = event.data.isNewSrc ? _seekedEvent1 : _seekedEvent2; - - var notRetry = event.data.seekRetries == undefined; - - if (notRetry || event.data.seekRetries == MAX_SEEK_RETRIES) // first retry - { - $(_audioElement).off(ev, onSeeked); - } - - if (DEBUG) - { - console.debug("onSeeked() #" + event.data.playId + " FIRST? " + notRetry + " EV: " + ev); - } - - var curTime = _audioElement.currentTime; - var diff = Math.abs(event.data.newCurrentTime - curTime); - - if((notRetry || event.data.seekRetries >= 0) && - diff >= 1) - { - if (DEBUG) - { - console.debug("onSeeked() time diff: " + event.data.newCurrentTime + " vs. " + curTime + " ("+diff+")"); - } - - if (notRetry) - { - event.data.seekRetries = MAX_SEEK_RETRIES; - - // if (DEBUG) - // { - // console.debug("onSeeked() fail => first retry, EV: " + _seekedEvent2); - // } - - event.data.isNewSrc = false; - //$(_audioElement).on(_seekedEvent2, event.data, onSeeked); - } - - //else - { - event.data.seekRetries--; - - if (DEBUG) - { - console.debug("onSeeked() FAIL => retry again (timeout)"); - } - - setTimeout(function() - { - onSeeked(event); - }, _Android ? 1000 : 200); - } - - setTimeout(function() - { - _audioElement.pause(); - try - { - _audioElement.currentTime = event.data.newCurrentTime; - } - catch (ex) - { - console.error(ex.message); - - setTimeout(function() - { - try - { - _audioElement.currentTime = event.data.newCurrentTime; - } - catch (ex) - { - console.error(ex.message); - } - }, 4); - } - }, 5); - } - else - { - if (DEBUG) - { - console.debug("onSeeked() STATE:"); - console.debug(notRetry); - console.debug(event.data.seekRetries); - console.debug(diff); - } - - if (diff >= 1) - { - if (DEBUG) - { - console.debug("onSeeked() ABORT, TRY AGAIN FROM SCRATCH!"); - } - - var smilSrc = _currentSmilSrc; - var epubSrc = _currentEpubSrc; - var seekBegin = event.data.newCurrentTime; - - self.reset(); - - setTimeout(function() - { - self.playFile(smilSrc, epubSrc, seekBegin); - }, 10); - - return; - } - - if (DEBUG) - { - console.debug("onSeeked() OKAY => play!"); - } - - event.data.seekRetries = undefined; - - self.play(); - - _audioElement.moSeeking = undefined; - } - } - }; - -})() -; -define("audioPlayer", ["readiumSDK"], (function (global) { - return function () { - var ret, fn; - return ret || global.audioPlayer; - }; -}(this))); - -/** - * @license RequireJS domReady 2.0.1 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved. - * Available via the MIT or new BSD license. - * see: http://github.com/requirejs/domReady for details - */ -/*jslint */ -/*global require: false, define: false, requirejs: false, - window: false, clearInterval: false, document: false, - self: false, setInterval: false */ - - -define('domReady',[],function () { - - - var isTop, testDiv, scrollIntervalId, - isBrowser = typeof window !== "undefined" && window.document, - isPageLoaded = !isBrowser, - doc = isBrowser ? document : null, - readyCalls = []; - - function runCallbacks(callbacks) { - var i; - for (i = 0; i < callbacks.length; i += 1) { - callbacks[i](doc); - } - } - - function callReady() { - var callbacks = readyCalls; - - if (isPageLoaded) { - //Call the DOM ready callbacks - if (callbacks.length) { - readyCalls = []; - runCallbacks(callbacks); - } - } - } - - /** - * Sets the page as loaded. - */ - function pageLoaded() { - if (!isPageLoaded) { - isPageLoaded = true; - if (scrollIntervalId) { - clearInterval(scrollIntervalId); - } - - callReady(); - } - } - - if (isBrowser) { - if (document.addEventListener) { - //Standards. Hooray! Assumption here that if standards based, - //it knows about DOMContentLoaded. - document.addEventListener("DOMContentLoaded", pageLoaded, false); - window.addEventListener("load", pageLoaded, false); - } else if (window.attachEvent) { - window.attachEvent("onload", pageLoaded); - - testDiv = document.createElement('div'); - try { - isTop = window.frameElement === null; - } catch (e) {} - - //DOMContentLoaded approximation that uses a doScroll, as found by - //Diego Perini: http://javascript.nwbox.com/IEContentLoaded/, - //but modified by other contributors, including jdalton - if (testDiv.doScroll && isTop && window.external) { - scrollIntervalId = setInterval(function () { - try { - testDiv.doScroll(); - pageLoaded(); - } catch (e) {} - }, 30); - } - } - - //Check if document already complete, and if so, just trigger page load - //listeners. Latest webkit browsers also use "interactive", and - //will fire the onDOMContentLoaded before "interactive" but not after - //entering "interactive" or "complete". More details: - //http://dev.w3.org/html5/spec/the-end.html#the-end - //http://stackoverflow.com/questions/3665561/document-readystate-of-interactive-vs-ondomcontentloaded - //Hmm, this is more complicated on further use, see "firing too early" - //bug: https://github.com/requirejs/domReady/issues/1 - //so removing the || document.readyState === "interactive" test. - //There is still a window.onload binding that should get fired if - //DOMContentLoaded is missed. - if (document.readyState === "complete") { - pageLoaded(); - } - } - - /** START OF PUBLIC API **/ - - /** - * Registers a callback for DOM ready. If DOM is already ready, the - * callback is called immediately. - * @param {Function} callback - */ - function domReady(callback) { - if (isPageLoaded) { - callback(doc); - } else { - readyCalls.push(callback); - } - return domReady; - } - - domReady.version = '2.0.1'; - - /** - * Loader Plugin API method - */ - domReady.load = function (name, req, onLoad, config) { - if (config.isBuild) { - onLoad(null); - } else { - domReady(onLoad); - } - }; - - /** END OF PUBLIC API **/ - - return domReady; -}); - -/** - * Rangy, a cross-browser JavaScript range and selection library - * http://code.google.com/p/rangy/ - * - * Copyright 2013, Tim Down - * Licensed under the MIT license. - * Version: 1.3alpha.804 - * Build date: 8 December 2013 - */ - -(function(global) { - var amdSupported = (typeof global.define == "function" && global.define.amd); - - var OBJECT = "object", FUNCTION = "function", UNDEFINED = "undefined"; - - // Minimal set of properties required for DOM Level 2 Range compliance. Comparison constants such as START_TO_START - // are omitted because ranges in KHTML do not have them but otherwise work perfectly well. See issue 113. - var domRangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed", - "commonAncestorContainer"]; - - // Minimal set of methods required for DOM Level 2 Range compliance - var domRangeMethods = ["setStart", "setStartBefore", "setStartAfter", "setEnd", "setEndBefore", - "setEndAfter", "collapse", "selectNode", "selectNodeContents", "compareBoundaryPoints", "deleteContents", - "extractContents", "cloneContents", "insertNode", "surroundContents", "cloneRange", "toString", "detach"]; - - var textRangeProperties = ["boundingHeight", "boundingLeft", "boundingTop", "boundingWidth", "htmlText", "text"]; - - // Subset of TextRange's full set of methods that we're interested in - var textRangeMethods = ["collapse", "compareEndPoints", "duplicate", "moveToElementText", "parentElement", "select", - "setEndPoint", "getBoundingClientRect"]; - - /*----------------------------------------------------------------------------------------------------------------*/ - - // Trio of functions taken from Peter Michaux's article: - // http://peter.michaux.ca/articles/feature-detection-state-of-the-art-browser-scripting - function isHostMethod(o, p) { - var t = typeof o[p]; - return t == FUNCTION || (!!(t == OBJECT && o[p])) || t == "unknown"; - } - - function isHostObject(o, p) { - return !!(typeof o[p] == OBJECT && o[p]); - } - - function isHostProperty(o, p) { - return typeof o[p] != UNDEFINED; - } - - // Creates a convenience function to save verbose repeated calls to tests functions - function createMultiplePropertyTest(testFunc) { - return function(o, props) { - var i = props.length; - while (i--) { - if (!testFunc(o, props[i])) { - return false; - } - } - return true; - }; - } - - // Next trio of functions are a convenience to save verbose repeated calls to previous two functions - var areHostMethods = createMultiplePropertyTest(isHostMethod); - var areHostObjects = createMultiplePropertyTest(isHostObject); - var areHostProperties = createMultiplePropertyTest(isHostProperty); - - function isTextRange(range) { - return range && areHostMethods(range, textRangeMethods) && areHostProperties(range, textRangeProperties); - } - - function getBody(doc) { - return isHostObject(doc, "body") ? doc.body : doc.getElementsByTagName("body")[0]; - } - - var modules = {}; - - var api = { - version: "1.3alpha.804", - initialized: false, - supported: true, - - util: { - isHostMethod: isHostMethod, - isHostObject: isHostObject, - isHostProperty: isHostProperty, - areHostMethods: areHostMethods, - areHostObjects: areHostObjects, - areHostProperties: areHostProperties, - isTextRange: isTextRange, - getBody: getBody - }, - - features: {}, - - modules: modules, - config: { - alertOnFail: true, - alertOnWarn: false, - preferTextRange: false - } - }; - - function consoleLog(msg) { - if (isHostObject(window, "console") && isHostMethod(window.console, "log")) { - window.console.log(msg); - } - } - - function alertOrLog(msg, shouldAlert) { - if (shouldAlert) { - window.alert(msg); - } else { - consoleLog(msg); - } - } - - function fail(reason) { - api.initialized = true; - api.supported = false; - alertOrLog("Rangy is not supported on this page in your browser. Reason: " + reason, api.config.alertOnFail); - } - - api.fail = fail; - - function warn(msg) { - alertOrLog("Rangy warning: " + msg, api.config.alertOnWarn); - } - - api.warn = warn; - - // Add utility extend() method - if ({}.hasOwnProperty) { - api.util.extend = function(obj, props, deep) { - var o, p; - for (var i in props) { - if (props.hasOwnProperty(i)) { - o = obj[i]; - p = props[i]; - //if (deep) alert([o !== null, typeof o == "object", p !== null, typeof p == "object"]) - if (deep && o !== null && typeof o == "object" && p !== null && typeof p == "object") { - api.util.extend(o, p, true); - } - obj[i] = p; - } - } - return obj; - }; - } else { - fail("hasOwnProperty not supported"); - } - - // Test whether Array.prototype.slice can be relied on for NodeLists and use an alternative toArray() if not - (function() { - var el = document.createElementNS("http://www.w3.org/1999/xhtml", "div"); - el.appendChild(document.createElementNS("http://www.w3.org/1999/xhtml", "span")); - var slice = [].slice; - var toArray; - try { - if (slice.call(el.childNodes, 0)[0].nodeType == 1) { - toArray = function(arrayLike) { - return slice.call(arrayLike, 0); - }; - } - } catch (e) {} - - if (!toArray) { - toArray = function(arrayLike) { - var arr = []; - for (var i = 0, len = arrayLike.length; i < len; ++i) { - arr[i] = arrayLike[i]; - } - return arr; - }; - } - - api.util.toArray = toArray; - })(); - - - // Very simple event handler wrapper function that doesn't attempt to solve issues such as "this" handling or - // normalization of event properties - var addListener; - if (isHostMethod(document, "addEventListener")) { - addListener = function(obj, eventType, listener) { - obj.addEventListener(eventType, listener, false); - }; - } else if (isHostMethod(document, "attachEvent")) { - addListener = function(obj, eventType, listener) { - obj.attachEvent("on" + eventType, listener); - }; - } else { - fail("Document does not have required addEventListener or attachEvent method"); - } - - api.util.addListener = addListener; - - var initListeners = []; - - function getErrorDesc(ex) { - return ex.message || ex.description || String(ex); - } - - // Initialization - function init() { - if (api.initialized) { - return; - } - var testRange; - var implementsDomRange = false, implementsTextRange = false; - - // First, perform basic feature tests - - if (isHostMethod(document, "createRange")) { - testRange = document.createRange(); - if (areHostMethods(testRange, domRangeMethods) && areHostProperties(testRange, domRangeProperties)) { - implementsDomRange = true; - } - testRange.detach(); - } - - var body = getBody(document); - if (!body || body.nodeName.toLowerCase() != "body") { - fail("No body element found"); - return; - } - - if (body && isHostMethod(body, "createTextRange")) { - testRange = body.createTextRange(); - if (isTextRange(testRange)) { - implementsTextRange = true; - } - } - - if (!implementsDomRange && !implementsTextRange) { - fail("Neither Range nor TextRange are available"); - return; - } - - api.initialized = true; - api.features = { - implementsDomRange: implementsDomRange, - implementsTextRange: implementsTextRange - }; - - // Initialize modules - var module, errorMessage; - for (var moduleName in modules) { - if ( (module = modules[moduleName]) instanceof Module ) { - module.init(module, api); - } - } - - // Call init listeners - for (var i = 0, len = initListeners.length; i < len; ++i) { - try { - initListeners[i](api); - } catch (ex) { - errorMessage = "Rangy init listener threw an exception. Continuing. Detail: " + getErrorDesc(ex); - consoleLog(errorMessage); - } - } - } - - // Allow external scripts to initialize this library in case it's loaded after the document has loaded - api.init = init; - - // Execute listener immediately if already initialized - api.addInitListener = function(listener) { - if (api.initialized) { - listener(api); - } else { - initListeners.push(listener); - } - }; - - var createMissingNativeApiListeners = []; - - api.addCreateMissingNativeApiListener = function(listener) { - createMissingNativeApiListeners.push(listener); - }; - - function createMissingNativeApi(win) { - win = win || window; - init(); - - // Notify listeners - for (var i = 0, len = createMissingNativeApiListeners.length; i < len; ++i) { - createMissingNativeApiListeners[i](win); - } - } - - api.createMissingNativeApi = createMissingNativeApi; - - function Module(name, dependencies, initializer) { - this.name = name; - this.dependencies = dependencies; - this.initialized = false; - this.supported = false; - this.initializer = initializer; - } - - Module.prototype = { - init: function(api) { - var requiredModuleNames = this.dependencies || []; - for (var i = 0, len = requiredModuleNames.length, requiredModule, moduleName; i < len; ++i) { - moduleName = requiredModuleNames[i]; - - requiredModule = modules[moduleName]; - if (!requiredModule || !(requiredModule instanceof Module)) { - throw new Error("required module '" + moduleName + "' not found"); - } - - requiredModule.init(); - - if (!requiredModule.supported) { - throw new Error("required module '" + moduleName + "' not supported"); - } - } - - // Now run initializer - this.initializer(this) - }, - - fail: function(reason) { - this.initialized = true; - this.supported = false; - throw new Error("Module '" + this.name + "' failed to load: " + reason); - }, - - warn: function(msg) { - api.warn("Module " + this.name + ": " + msg); - }, - - deprecationNotice: function(deprecated, replacement) { - api.warn("DEPRECATED: " + deprecated + " in module " + this.name + "is deprecated. Please use " - + replacement + " instead"); - }, - - createError: function(msg) { - return new Error("Error in Rangy " + this.name + " module: " + msg); - } - }; - - function createModule(isCore, name, dependencies, initFunc) { - var newModule = new Module(name, dependencies, function(module) { - if (!module.initialized) { - module.initialized = true; - try { - initFunc(api, module); - module.supported = true; - } catch (ex) { - var errorMessage = "Module '" + name + "' failed to load: " + getErrorDesc(ex); - consoleLog(errorMessage); - } - } - }); - modules[name] = newModule; - -/* - // Add module AMD support - if (!isCore && amdSupported) { - global.define(["rangy-core"], function(rangy) { - - }); - } -*/ - } - - api.createModule = function(name) { - // Allow 2 or 3 arguments (second argument is an optional array of dependencies) - var initFunc, dependencies; - if (arguments.length == 2) { - initFunc = arguments[1]; - dependencies = []; - } else { - initFunc = arguments[2]; - dependencies = arguments[1]; - } - createModule(false, name, dependencies, initFunc); - }; - - api.createCoreModule = function(name, dependencies, initFunc) { - createModule(true, name, dependencies, initFunc); - }; - - /*----------------------------------------------------------------------------------------------------------------*/ - - // Ensure rangy.rangePrototype and rangy.selectionPrototype are available immediately - - function RangePrototype() {} - api.RangePrototype = RangePrototype; - api.rangePrototype = new RangePrototype(); - - function SelectionPrototype() {} - api.selectionPrototype = new SelectionPrototype(); - - /*----------------------------------------------------------------------------------------------------------------*/ - - // Wait for document to load before running tests - - var docReady = false; - - var loadHandler = function(e) { - if (!docReady) { - docReady = true; - if (!api.initialized) { - init(); - } - } - }; - - // Test whether we have window and document objects that we will need - if (typeof window == UNDEFINED) { - fail("No window found"); - return; - } - if (typeof document == UNDEFINED) { - fail("No document found"); - return; - } - - if (isHostMethod(document, "addEventListener")) { - document.addEventListener("DOMContentLoaded", loadHandler, false); - } - - // Add a fallback in case the DOMContentLoaded event isn't supported - addListener(window, "load", loadHandler); - - /*----------------------------------------------------------------------------------------------------------------*/ - - // AMD, for those who like this kind of thing - - /* - if (amdSupported) { - // AMD. Register as an anonymous module. - global.define(function() { - api.amd = true; - return api; - }); - } - */ - // Create a "rangy" property of the global object in any case. Other Rangy modules (which use Rangy's own simple - // module system) rely on the existence of this global property - global.rangy = api; -})(this); - -rangy.createCoreModule("DomUtil", [], function(api, module) { - var UNDEF = "undefined"; - var util = api.util; - - // Perform feature tests - if (!util.areHostMethods(document, ["createDocumentFragment", "createElement", "createTextNode"])) { - module.fail("document missing a Node creation method"); - } - - if (!util.isHostMethod(document, "getElementsByTagName")) { - module.fail("document missing getElementsByTagName method"); - } - - var el = document.createElementNS("http://www.w3.org/1999/xhtml", "div"); - if (!util.areHostMethods(el, ["insertBefore", "appendChild", "cloneNode"] || - !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]))) { - module.fail("Incomplete Element implementation"); - } - - // innerHTML is required for Range's createContextualFragment method - if (!util.isHostProperty(el, "innerHTML")) { - module.fail("Element is missing innerHTML property"); - } - - var textNode = document.createTextNode("test"); - if (!util.areHostMethods(textNode, ["splitText", "deleteData", "insertData", "appendData", "cloneNode"] || - !util.areHostObjects(el, ["previousSibling", "nextSibling", "childNodes", "parentNode"]) || - !util.areHostProperties(textNode, ["data"]))) { - module.fail("Incomplete Text Node implementation"); - } - - /*----------------------------------------------------------------------------------------------------------------*/ - - // Removed use of indexOf because of a bizarre bug in Opera that is thrown in one of the Acid3 tests. I haven't been - // able to replicate it outside of the test. The bug is that indexOf returns -1 when called on an Array that - // contains just the document as a single element and the value searched for is the document. - var arrayContains = /*Array.prototype.indexOf ? - function(arr, val) { - return arr.indexOf(val) > -1; - }:*/ - - function(arr, val) { - var i = arr.length; - while (i--) { - if (arr[i] === val) { - return true; - } - } - return false; - }; - - // Opera 11 puts HTML elements in the null namespace, it seems, and IE 7 has undefined namespaceURI - function isHtmlNamespace(node) { - var ns; - return typeof node.namespaceURI == UNDEF || ((ns = node.namespaceURI) === null || ns == "http://www.w3.org/1999/xhtml"); - } - - function parentElement(node) { - var parent = node.parentNode; - return (parent.nodeType == 1) ? parent : null; - } - - function getNodeIndex(node) { - var i = 0; - while( (node = node.previousSibling) ) { - ++i; - } - return i; - } - - function getNodeLength(node) { - switch (node.nodeType) { - case 7: - case 10: - return 0; - case 3: - case 8: - return node.length; - default: - return node.childNodes.length; - } - } - - function getCommonAncestor(node1, node2) { - var ancestors = [], n; - for (n = node1; n; n = n.parentNode) { - ancestors.push(n); - } - - for (n = node2; n; n = n.parentNode) { - if (arrayContains(ancestors, n)) { - return n; - } - } - - return null; - } - - function isAncestorOf(ancestor, descendant, selfIsAncestor) { - var n = selfIsAncestor ? descendant : descendant.parentNode; - while (n) { - if (n === ancestor) { - return true; - } else { - n = n.parentNode; - } - } - return false; - } - - function isOrIsAncestorOf(ancestor, descendant) { - return isAncestorOf(ancestor, descendant, true); - } - - function getClosestAncestorIn(node, ancestor, selfIsAncestor) { - var p, n = selfIsAncestor ? node : node.parentNode; - while (n) { - p = n.parentNode; - if (p === ancestor) { - return n; - } - n = p; - } - return null; - } - - function isCharacterDataNode(node) { - var t = node.nodeType; - return t == 3 || t == 4 || t == 8 ; // Text, CDataSection or Comment - } - - function isTextOrCommentNode(node) { - if (!node) { - return false; - } - var t = node.nodeType; - return t == 3 || t == 8 ; // Text or Comment - } - - function insertAfter(node, precedingNode) { - var nextNode = precedingNode.nextSibling, parent = precedingNode.parentNode; - if (nextNode) { - parent.insertBefore(node, nextNode); - } else { - parent.appendChild(node); - } - return node; - } - - // Note that we cannot use splitText() because it is bugridden in IE 9. - function splitDataNode(node, index, positionsToPreserve) { - var newNode = node.cloneNode(false); - newNode.deleteData(0, index); - node.deleteData(index, node.length - index); - insertAfter(newNode, node); - - // Preserve positions - if (positionsToPreserve) { - for (var i = 0, position; position = positionsToPreserve[i++]; ) { - // Handle case where position was inside the portion of node after the split point - if (position.node == node && position.offset > index) { - position.node = newNode; - position.offset -= index; - } - // Handle the case where the position is a node offset within node's parent - else if (position.node == node.parentNode && position.offset > getNodeIndex(node)) { - ++position.offset; - } - } - } - return newNode; - } - - function getDocument(node) { - if (node.nodeType == 9) { - return node; - } else if (typeof node.ownerDocument != UNDEF) { - return node.ownerDocument; - } else if (typeof node.document != UNDEF) { - return node.document; - } else if (node.parentNode) { - return getDocument(node.parentNode); - } else { - throw module.createError("getDocument: no document found for node"); - } - } - - function getWindow(node) { - var doc = getDocument(node); - if (typeof doc.defaultView != UNDEF) { - return doc.defaultView; - } else if (typeof doc.parentWindow != UNDEF) { - return doc.parentWindow; - } else { - throw module.createError("Cannot get a window object for node"); - } - } - - function getIframeDocument(iframeEl) { - if (typeof iframeEl.contentDocument != UNDEF) { - return iframeEl.contentDocument; - } else if (typeof iframeEl.contentWindow != UNDEF) { - return iframeEl.contentWindow.document; - } else { - throw module.createError("getIframeDocument: No Document object found for iframe element"); - } - } - - function getIframeWindow(iframeEl) { - if (typeof iframeEl.contentWindow != UNDEF) { - return iframeEl.contentWindow; - } else if (typeof iframeEl.contentDocument != UNDEF) { - return iframeEl.contentDocument.defaultView; - } else { - throw module.createError("getIframeWindow: No Window object found for iframe element"); - } - } - - // This looks bad. Is it worth it? - function isWindow(obj) { - return obj && util.isHostMethod(obj, "setTimeout") && util.isHostObject(obj, "document"); - } - - function getContentDocument(obj, module, methodName) { - var doc; - - if (!obj) { - doc = document; - } - - // Test if a DOM node has been passed and obtain a document object for it if so - else if (util.isHostProperty(obj, "nodeType")) { - doc = (obj.nodeType == 1 && obj.tagName.toLowerCase() == "iframe") - ? getIframeDocument(obj) : getDocument(obj); - } - - // Test if the doc parameter appears to be a Window object - else if (isWindow(obj)) { - doc = obj.document; - } - - if (!doc) { - throw module.createError(methodName + "(): Parameter must be a Window object or DOM node"); - } - - return doc; - } - - function getRootContainer(node) { - var parent; - while ( (parent = node.parentNode) ) { - node = parent; - } - return node; - } - - function comparePoints(nodeA, offsetA, nodeB, offsetB) { - // See http://www.w3.org/TR/DOM-Level-2-Traversal-Range/ranges.html#Level-2-Range-Comparing - var nodeC, root, childA, childB, n; - if (nodeA == nodeB) { - // Case 1: nodes are the same - return offsetA === offsetB ? 0 : (offsetA < offsetB) ? -1 : 1; - } else if ( (nodeC = getClosestAncestorIn(nodeB, nodeA, true)) ) { - // Case 2: node C (container B or an ancestor) is a child node of A - return offsetA <= getNodeIndex(nodeC) ? -1 : 1; - } else if ( (nodeC = getClosestAncestorIn(nodeA, nodeB, true)) ) { - // Case 3: node C (container A or an ancestor) is a child node of B - return getNodeIndex(nodeC) < offsetB ? -1 : 1; - } else { - root = getCommonAncestor(nodeA, nodeB); - if (!root) { - throw new Error("comparePoints error: nodes have no common ancestor"); - } - - // Case 4: containers are siblings or descendants of siblings - childA = (nodeA === root) ? root : getClosestAncestorIn(nodeA, root, true); - childB = (nodeB === root) ? root : getClosestAncestorIn(nodeB, root, true); - - if (childA === childB) { - // This shouldn't be possible - throw module.createError("comparePoints got to case 4 and childA and childB are the same!"); - } else { - n = root.firstChild; - while (n) { - if (n === childA) { - return -1; - } else if (n === childB) { - return 1; - } - n = n.nextSibling; - } - } - } - } - - /*----------------------------------------------------------------------------------------------------------------*/ - - // Test for IE's crash (IE 6/7) or exception (IE >= 8) when a reference to garbage-collected text node is queried - var crashyTextNodes = false; - - function isBrokenNode(node) { - try { - node.parentNode; - return false; - } catch (e) { - return true; - } - } - - (function() { - var el = document.createElementNS("http://www.w3.org/1999/xhtml", "b"); - el.innerHTML = "1"; - var textNode = el.firstChild; - el.innerHTML = "
"; - crashyTextNodes = isBrokenNode(textNode); - - api.features.crashyTextNodes = crashyTextNodes; - })(); - - /*----------------------------------------------------------------------------------------------------------------*/ - - function inspectNode(node) { - if (!node) { - return "[No node]"; - } - if (crashyTextNodes && isBrokenNode(node)) { - return "[Broken node]"; - } - if (isCharacterDataNode(node)) { - return '"' + node.data + '"'; - } - if (node.nodeType == 1) { - var idAttr = node.id ? ' id="' + node.id + '"' : ""; - return "<" + node.nodeName + idAttr + ">[" + getNodeIndex(node) + "][" + node.childNodes.length + "][" + (node.innerHTML || "[innerHTML not supported]").slice(0, 25) + "]"; - } - return node.nodeName; - } - - function fragmentFromNodeChildren(node) { - var fragment = getDocument(node).createDocumentFragment(), child; - while ( (child = node.firstChild) ) { - fragment.appendChild(child); - } - return fragment; - } - - var getComputedStyleProperty; - if (typeof window.getComputedStyle != UNDEF) { - getComputedStyleProperty = function(el, propName) { - return getWindow(el).getComputedStyle(el, null)[propName]; - }; - } else if (typeof document.documentElement.currentStyle != UNDEF) { - getComputedStyleProperty = function(el, propName) { - return el.currentStyle[propName]; - }; - } else { - module.fail("No means of obtaining computed style properties found"); - } - - function NodeIterator(root) { - this.root = root; - this._next = root; - } - - NodeIterator.prototype = { - _current: null, - - hasNext: function() { - return !!this._next; - }, - - next: function() { - var n = this._current = this._next; - var child, next; - if (this._current) { - child = n.firstChild; - if (child) { - this._next = child; - } else { - next = null; - while ((n !== this.root) && !(next = n.nextSibling)) { - n = n.parentNode; - } - this._next = next; - } - } - return this._current; - }, - - detach: function() { - this._current = this._next = this.root = null; - } - }; - - function createIterator(root) { - return new NodeIterator(root); - } - - function DomPosition(node, offset) { - this.node = node; - this.offset = offset; - } - - DomPosition.prototype = { - equals: function(pos) { - return !!pos && this.node === pos.node && this.offset == pos.offset; - }, - - inspect: function() { - return "[DomPosition(" + inspectNode(this.node) + ":" + this.offset + ")]"; - }, - - toString: function() { - return this.inspect(); - } - }; - - function DOMException(codeName) { - this.code = this[codeName]; - this.codeName = codeName; - this.message = "DOMException: " + this.codeName; - } - - DOMException.prototype = { - INDEX_SIZE_ERR: 1, - HIERARCHY_REQUEST_ERR: 3, - WRONG_DOCUMENT_ERR: 4, - NO_MODIFICATION_ALLOWED_ERR: 7, - NOT_FOUND_ERR: 8, - NOT_SUPPORTED_ERR: 9, - INVALID_STATE_ERR: 11 - }; - - DOMException.prototype.toString = function() { - return this.message; - }; - - api.dom = { - arrayContains: arrayContains, - isHtmlNamespace: isHtmlNamespace, - parentElement: parentElement, - getNodeIndex: getNodeIndex, - getNodeLength: getNodeLength, - getCommonAncestor: getCommonAncestor, - isAncestorOf: isAncestorOf, - isOrIsAncestorOf: isOrIsAncestorOf, - getClosestAncestorIn: getClosestAncestorIn, - isCharacterDataNode: isCharacterDataNode, - isTextOrCommentNode: isTextOrCommentNode, - insertAfter: insertAfter, - splitDataNode: splitDataNode, - getDocument: getDocument, - getWindow: getWindow, - getIframeWindow: getIframeWindow, - getIframeDocument: getIframeDocument, - getBody: util.getBody, - isWindow: isWindow, - getContentDocument: getContentDocument, - getRootContainer: getRootContainer, - comparePoints: comparePoints, - isBrokenNode: isBrokenNode, - inspectNode: inspectNode, - getComputedStyleProperty: getComputedStyleProperty, - fragmentFromNodeChildren: fragmentFromNodeChildren, - createIterator: createIterator, - DomPosition: DomPosition - }; - - api.DOMException = DOMException; -}); -rangy.createCoreModule("DomRange", ["DomUtil"], function(api, module) { - var dom = api.dom; - var util = api.util; - var DomPosition = dom.DomPosition; - var DOMException = api.DOMException; - - var isCharacterDataNode = dom.isCharacterDataNode; - var getNodeIndex = dom.getNodeIndex; - var isOrIsAncestorOf = dom.isOrIsAncestorOf; - var getDocument = dom.getDocument; - var comparePoints = dom.comparePoints; - var splitDataNode = dom.splitDataNode; - var getClosestAncestorIn = dom.getClosestAncestorIn; - var getNodeLength = dom.getNodeLength; - var arrayContains = dom.arrayContains; - var getRootContainer = dom.getRootContainer; - var crashyTextNodes = api.features.crashyTextNodes; - - /*----------------------------------------------------------------------------------------------------------------*/ - - // Utility functions - - function isNonTextPartiallySelected(node, range) { - return (node.nodeType != 3) && - (isOrIsAncestorOf(node, range.startContainer) || isOrIsAncestorOf(node, range.endContainer)); - } - - function getRangeDocument(range) { - return range.document || getDocument(range.startContainer); - } - - function getBoundaryBeforeNode(node) { - return new DomPosition(node.parentNode, getNodeIndex(node)); - } - - function getBoundaryAfterNode(node) { - return new DomPosition(node.parentNode, getNodeIndex(node) + 1); - } - - function insertNodeAtPosition(node, n, o) { - var firstNodeInserted = node.nodeType == 11 ? node.firstChild : node; - if (isCharacterDataNode(n)) { - if (o == n.length) { - dom.insertAfter(node, n); - } else { - n.parentNode.insertBefore(node, o == 0 ? n : splitDataNode(n, o)); - } - } else if (o >= n.childNodes.length) { - n.appendChild(node); - } else { - n.insertBefore(node, n.childNodes[o]); - } - return firstNodeInserted; - } - - function rangesIntersect(rangeA, rangeB, touchingIsIntersecting) { - assertRangeValid(rangeA); - assertRangeValid(rangeB); - - if (getRangeDocument(rangeB) != getRangeDocument(rangeA)) { - throw new DOMException("WRONG_DOCUMENT_ERR"); - } - - var startComparison = comparePoints(rangeA.startContainer, rangeA.startOffset, rangeB.endContainer, rangeB.endOffset), - endComparison = comparePoints(rangeA.endContainer, rangeA.endOffset, rangeB.startContainer, rangeB.startOffset); - - return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0; - } - - function cloneSubtree(iterator) { - var partiallySelected; - for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) { - partiallySelected = iterator.isPartiallySelectedSubtree(); - node = node.cloneNode(!partiallySelected); - if (partiallySelected) { - subIterator = iterator.getSubtreeIterator(); - node.appendChild(cloneSubtree(subIterator)); - subIterator.detach(true); - } - - if (node.nodeType == 10) { // DocumentType - throw new DOMException("HIERARCHY_REQUEST_ERR"); - } - frag.appendChild(node); - } - return frag; - } - - function iterateSubtree(rangeIterator, func, iteratorState) { - var it, n; - iteratorState = iteratorState || { stop: false }; - for (var node, subRangeIterator; node = rangeIterator.next(); ) { - if (rangeIterator.isPartiallySelectedSubtree()) { - if (func(node) === false) { - iteratorState.stop = true; - return; - } else { - // The node is partially selected by the Range, so we can use a new RangeIterator on the portion of - // the node selected by the Range. - subRangeIterator = rangeIterator.getSubtreeIterator(); - iterateSubtree(subRangeIterator, func, iteratorState); - subRangeIterator.detach(true); - if (iteratorState.stop) { - return; - } - } - } else { - // The whole node is selected, so we can use efficient DOM iteration to iterate over the node and its - // descendants - it = dom.createIterator(node); - while ( (n = it.next()) ) { - if (func(n) === false) { - iteratorState.stop = true; - return; - } - } - } - } - } - - function deleteSubtree(iterator) { - var subIterator; - while (iterator.next()) { - if (iterator.isPartiallySelectedSubtree()) { - subIterator = iterator.getSubtreeIterator(); - deleteSubtree(subIterator); - subIterator.detach(true); - } else { - iterator.remove(); - } - } - } - - function extractSubtree(iterator) { - for (var node, frag = getRangeDocument(iterator.range).createDocumentFragment(), subIterator; node = iterator.next(); ) { - - if (iterator.isPartiallySelectedSubtree()) { - node = node.cloneNode(false); - subIterator = iterator.getSubtreeIterator(); - node.appendChild(extractSubtree(subIterator)); - subIterator.detach(true); - } else { - iterator.remove(); - } - if (node.nodeType == 10) { // DocumentType - throw new DOMException("HIERARCHY_REQUEST_ERR"); - } - frag.appendChild(node); - } - return frag; - } - - function getNodesInRange(range, nodeTypes, filter) { - var filterNodeTypes = !!(nodeTypes && nodeTypes.length), regex; - var filterExists = !!filter; - if (filterNodeTypes) { - regex = new RegExp("^(" + nodeTypes.join("|") + ")$"); - } - - var nodes = []; - iterateSubtree(new RangeIterator(range, false), function(node) { - if (filterNodeTypes && !regex.test(node.nodeType)) { - return; - } - if (filterExists && !filter(node)) { - return; - } - // Don't include a boundary container if it is a character data node and the range does not contain any - // of its character data. See issue 190. - var sc = range.startContainer; - if (node == sc && isCharacterDataNode(sc) && range.startOffset == sc.length) { - return; - } - - var ec = range.endContainer; - if (node == ec && isCharacterDataNode(ec) && range.endOffset == 0) { - return; - } - - nodes.push(node); - }); - return nodes; - } - - function inspect(range) { - var name = (typeof range.getName == "undefined") ? "Range" : range.getName(); - return "[" + name + "(" + dom.inspectNode(range.startContainer) + ":" + range.startOffset + ", " + - dom.inspectNode(range.endContainer) + ":" + range.endOffset + ")]"; - } - - /*----------------------------------------------------------------------------------------------------------------*/ - - // RangeIterator code partially borrows from IERange by Tim Ryan (http://github.com/timcameronryan/IERange) - - function RangeIterator(range, clonePartiallySelectedTextNodes) { - this.range = range; - this.clonePartiallySelectedTextNodes = clonePartiallySelectedTextNodes; - - - if (!range.collapsed) { - this.sc = range.startContainer; - this.so = range.startOffset; - this.ec = range.endContainer; - this.eo = range.endOffset; - var root = range.commonAncestorContainer; - - if (this.sc === this.ec && isCharacterDataNode(this.sc)) { - this.isSingleCharacterDataNode = true; - this._first = this._last = this._next = this.sc; - } else { - this._first = this._next = (this.sc === root && !isCharacterDataNode(this.sc)) ? - this.sc.childNodes[this.so] : getClosestAncestorIn(this.sc, root, true); - this._last = (this.ec === root && !isCharacterDataNode(this.ec)) ? - this.ec.childNodes[this.eo - 1] : getClosestAncestorIn(this.ec, root, true); - } - } - } - - RangeIterator.prototype = { - _current: null, - _next: null, - _first: null, - _last: null, - isSingleCharacterDataNode: false, - - reset: function() { - this._current = null; - this._next = this._first; - }, - - hasNext: function() { - return !!this._next; - }, - - next: function() { - // Move to next node - var current = this._current = this._next; - if (current) { - this._next = (current !== this._last) ? current.nextSibling : null; - - // Check for partially selected text nodes - if (isCharacterDataNode(current) && this.clonePartiallySelectedTextNodes) { - if (current === this.ec) { - (current = current.cloneNode(true)).deleteData(this.eo, current.length - this.eo); - } - if (this._current === this.sc) { - (current = current.cloneNode(true)).deleteData(0, this.so); - } - } - } - - return current; - }, - - remove: function() { - var current = this._current, start, end; - - if (isCharacterDataNode(current) && (current === this.sc || current === this.ec)) { - start = (current === this.sc) ? this.so : 0; - end = (current === this.ec) ? this.eo : current.length; - if (start != end) { - current.deleteData(start, end - start); - } - } else { - if (current.parentNode) { - current.parentNode.removeChild(current); - } else { - } - } - }, - - // Checks if the current node is partially selected - isPartiallySelectedSubtree: function() { - var current = this._current; - return isNonTextPartiallySelected(current, this.range); - }, - - getSubtreeIterator: function() { - var subRange; - if (this.isSingleCharacterDataNode) { - subRange = this.range.cloneRange(); - subRange.collapse(false); - } else { - subRange = new Range(getRangeDocument(this.range)); - var current = this._current; - var startContainer = current, startOffset = 0, endContainer = current, endOffset = getNodeLength(current); - - if (isOrIsAncestorOf(current, this.sc)) { - startContainer = this.sc; - startOffset = this.so; - } - if (isOrIsAncestorOf(current, this.ec)) { - endContainer = this.ec; - endOffset = this.eo; - } - - updateBoundaries(subRange, startContainer, startOffset, endContainer, endOffset); - } - return new RangeIterator(subRange, this.clonePartiallySelectedTextNodes); - }, - - detach: function(detachRange) { - if (detachRange) { - this.range.detach(); - } - this.range = this._current = this._next = this._first = this._last = this.sc = this.so = this.ec = this.eo = null; - } - }; - - /*----------------------------------------------------------------------------------------------------------------*/ - - // Exceptions - - function RangeException(codeName) { - this.code = this[codeName]; - this.codeName = codeName; - this.message = "RangeException: " + this.codeName; - } - - RangeException.prototype = { - BAD_BOUNDARYPOINTS_ERR: 1, - INVALID_NODE_TYPE_ERR: 2 - }; - - RangeException.prototype.toString = function() { - return this.message; - }; - - /*----------------------------------------------------------------------------------------------------------------*/ - - var beforeAfterNodeTypes = [1, 3, 4, 5, 7, 8, 10]; - var rootContainerNodeTypes = [2, 9, 11]; - var readonlyNodeTypes = [5, 6, 10, 12]; - var insertableNodeTypes = [1, 3, 4, 5, 7, 8, 10, 11]; - var surroundNodeTypes = [1, 3, 4, 5, 7, 8]; - - function createAncestorFinder(nodeTypes) { - return function(node, selfIsAncestor) { - var t, n = selfIsAncestor ? node : node.parentNode; - while (n) { - t = n.nodeType; - if (arrayContains(nodeTypes, t)) { - return n; - } - n = n.parentNode; - } - return null; - }; - } - - var getDocumentOrFragmentContainer = createAncestorFinder( [9, 11] ); - var getReadonlyAncestor = createAncestorFinder(readonlyNodeTypes); - var getDocTypeNotationEntityAncestor = createAncestorFinder( [6, 10, 12] ); - - function assertNoDocTypeNotationEntityAncestor(node, allowSelf) { - if (getDocTypeNotationEntityAncestor(node, allowSelf)) { - throw new RangeException("INVALID_NODE_TYPE_ERR"); - } - } - - function assertNotDetached(range) { - if (!range.startContainer) { - throw new DOMException("INVALID_STATE_ERR"); - } - } - - function assertValidNodeType(node, invalidTypes) { - if (!arrayContains(invalidTypes, node.nodeType)) { - throw new RangeException("INVALID_NODE_TYPE_ERR"); - } - } - - function assertValidOffset(node, offset) { - if (offset < 0 || offset > (isCharacterDataNode(node) ? node.length : node.childNodes.length)) { - throw new DOMException("INDEX_SIZE_ERR"); - } - } - - function assertSameDocumentOrFragment(node1, node2) { - if (getDocumentOrFragmentContainer(node1, true) !== getDocumentOrFragmentContainer(node2, true)) { - throw new DOMException("WRONG_DOCUMENT_ERR"); - } - } - - function assertNodeNotReadOnly(node) { - if (getReadonlyAncestor(node, true)) { - throw new DOMException("NO_MODIFICATION_ALLOWED_ERR"); - } - } - - function assertNode(node, codeName) { - if (!node) { - throw new DOMException(codeName); - } - } - - function isOrphan(node) { - return (crashyTextNodes && dom.isBrokenNode(node)) || - !arrayContains(rootContainerNodeTypes, node.nodeType) && !getDocumentOrFragmentContainer(node, true); - } - - function isValidOffset(node, offset) { - return offset <= (isCharacterDataNode(node) ? node.length : node.childNodes.length); - } - - function isRangeValid(range) { - return (!!range.startContainer && !!range.endContainer - && !isOrphan(range.startContainer) - && !isOrphan(range.endContainer) - && isValidOffset(range.startContainer, range.startOffset) - && isValidOffset(range.endContainer, range.endOffset)); - } - - function assertRangeValid(range) { - assertNotDetached(range); - if (!isRangeValid(range)) { - throw new Error("Range error: Range is no longer valid after DOM mutation (" + range.inspect() + ")"); - } - } - - /*----------------------------------------------------------------------------------------------------------------*/ - - // Test the browser's innerHTML support to decide how to implement createContextualFragment - var styleEl = document.createElementNS("http://www.w3.org/1999/xhtml", "style"); - var htmlParsingConforms = false; - try { - styleEl.innerHTML = "x"; - htmlParsingConforms = (styleEl.firstChild.nodeType == 3); // Opera incorrectly creates an element node - } catch (e) { - // IE 6 and 7 throw - } - - api.features.htmlParsingConforms = htmlParsingConforms; - - var createContextualFragment = htmlParsingConforms ? - - // Implementation as per HTML parsing spec, trusting in the browser's implementation of innerHTML. See - // discussion and base code for this implementation at issue 67. - // Spec: http://html5.org/specs/dom-parsing.html#extensions-to-the-range-interface - // Thanks to Aleks Williams. - function(fragmentStr) { - // "Let node the context object's start's node." - var node = this.startContainer; - var doc = getDocument(node); - - // "If the context object's start's node is null, raise an INVALID_STATE_ERR - // exception and abort these steps." - if (!node) { - throw new DOMException("INVALID_STATE_ERR"); - } - - // "Let element be as follows, depending on node's interface:" - // Document, Document Fragment: null - var el = null; - - // "Element: node" - if (node.nodeType == 1) { - el = node; - - // "Text, Comment: node's parentElement" - } else if (isCharacterDataNode(node)) { - el = dom.parentElement(node); - } - - // "If either element is null or element's ownerDocument is an HTML document - // and element's local name is "html" and element's namespace is the HTML - // namespace" - if (el === null || ( - el.nodeName == "HTML" - && dom.isHtmlNamespace(getDocument(el).documentElement) - && dom.isHtmlNamespace(el) - )) { - - // "let element be a new Element with "body" as its local name and the HTML - // namespace as its namespace."" - el = doc.createElementNS("http://www.w3.org/1999/xhtml", "body"); - } else { - el = el.cloneNode(false); - } - - // "If the node's document is an HTML document: Invoke the HTML fragment parsing algorithm." - // "If the node's document is an XML document: Invoke the XML fragment parsing algorithm." - // "In either case, the algorithm must be invoked with fragment as the input - // and element as the context element." - el.innerHTML = fragmentStr; - - // "If this raises an exception, then abort these steps. Otherwise, let new - // children be the nodes returned." - - // "Let fragment be a new DocumentFragment." - // "Append all new children to fragment." - // "Return fragment." - return dom.fragmentFromNodeChildren(el); - } : - - // In this case, innerHTML cannot be trusted, so fall back to a simpler, non-conformant implementation that - // previous versions of Rangy used (with the exception of using a body element rather than a div) - function(fragmentStr) { - assertNotDetached(this); - var doc = getRangeDocument(this); - var el = doc.createElementNS("http://www.w3.org/1999/xhtml", "body"); - el.innerHTML = fragmentStr; - - return dom.fragmentFromNodeChildren(el); - }; - - function splitRangeBoundaries(range, positionsToPreserve) { - assertRangeValid(range); - - var sc = range.startContainer, so = range.startOffset, ec = range.endContainer, eo = range.endOffset; - var startEndSame = (sc === ec); - - if (isCharacterDataNode(ec) && eo > 0 && eo < ec.length) { - splitDataNode(ec, eo, positionsToPreserve); - } - - if (isCharacterDataNode(sc) && so > 0 && so < sc.length) { - sc = splitDataNode(sc, so, positionsToPreserve); - if (startEndSame) { - eo -= so; - ec = sc; - } else if (ec == sc.parentNode && eo >= getNodeIndex(sc)) { - eo++; - } - so = 0; - } - range.setStartAndEnd(sc, so, ec, eo); - } - - /*----------------------------------------------------------------------------------------------------------------*/ - - var rangeProperties = ["startContainer", "startOffset", "endContainer", "endOffset", "collapsed", - "commonAncestorContainer"]; - - var s2s = 0, s2e = 1, e2e = 2, e2s = 3; - var n_b = 0, n_a = 1, n_b_a = 2, n_i = 3; - - util.extend(api.rangePrototype, { - compareBoundaryPoints: function(how, range) { - assertRangeValid(this); - assertSameDocumentOrFragment(this.startContainer, range.startContainer); - - var nodeA, offsetA, nodeB, offsetB; - var prefixA = (how == e2s || how == s2s) ? "start" : "end"; - var prefixB = (how == s2e || how == s2s) ? "start" : "end"; - nodeA = this[prefixA + "Container"]; - offsetA = this[prefixA + "Offset"]; - nodeB = range[prefixB + "Container"]; - offsetB = range[prefixB + "Offset"]; - return comparePoints(nodeA, offsetA, nodeB, offsetB); - }, - - insertNode: function(node) { - assertRangeValid(this); - assertValidNodeType(node, insertableNodeTypes); - assertNodeNotReadOnly(this.startContainer); - - if (isOrIsAncestorOf(node, this.startContainer)) { - throw new DOMException("HIERARCHY_REQUEST_ERR"); - } - - // No check for whether the container of the start of the Range is of a type that does not allow - // children of the type of node: the browser's DOM implementation should do this for us when we attempt - // to add the node - - var firstNodeInserted = insertNodeAtPosition(node, this.startContainer, this.startOffset); - this.setStartBefore(firstNodeInserted); - }, - - cloneContents: function() { - assertRangeValid(this); - - var clone, frag; - if (this.collapsed) { - return getRangeDocument(this).createDocumentFragment(); - } else { - if (this.startContainer === this.endContainer && isCharacterDataNode(this.startContainer)) { - clone = this.startContainer.cloneNode(true); - clone.data = clone.data.slice(this.startOffset, this.endOffset); - frag = getRangeDocument(this).createDocumentFragment(); - frag.appendChild(clone); - return frag; - } else { - var iterator = new RangeIterator(this, true); - clone = cloneSubtree(iterator); - iterator.detach(); - } - return clone; - } - }, - - canSurroundContents: function() { - assertRangeValid(this); - assertNodeNotReadOnly(this.startContainer); - assertNodeNotReadOnly(this.endContainer); - - // Check if the contents can be surrounded. Specifically, this means whether the range partially selects - // no non-text nodes. - var iterator = new RangeIterator(this, true); - var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) || - (iterator._last && isNonTextPartiallySelected(iterator._last, this))); - iterator.detach(); - return !boundariesInvalid; - }, - - surroundContents: function(node) { - assertValidNodeType(node, surroundNodeTypes); - - if (!this.canSurroundContents()) { - throw new RangeException("BAD_BOUNDARYPOINTS_ERR"); - } - - // Extract the contents - var content = this.extractContents(); - - // Clear the children of the node - if (node.hasChildNodes()) { - while (node.lastChild) { - node.removeChild(node.lastChild); - } - } - - // Insert the new node and add the extracted contents - insertNodeAtPosition(node, this.startContainer, this.startOffset); - node.appendChild(content); - - this.selectNode(node); - }, - - cloneRange: function() { - assertRangeValid(this); - var range = new Range(getRangeDocument(this)); - var i = rangeProperties.length, prop; - while (i--) { - prop = rangeProperties[i]; - range[prop] = this[prop]; - } - return range; - }, - - toString: function() { - assertRangeValid(this); - var sc = this.startContainer; - if (sc === this.endContainer && isCharacterDataNode(sc)) { - return (sc.nodeType == 3 || sc.nodeType == 4) ? sc.data.slice(this.startOffset, this.endOffset) : ""; - } else { - var textParts = [], iterator = new RangeIterator(this, true); - iterateSubtree(iterator, function(node) { - // Accept only text or CDATA nodes, not comments - if (node.nodeType == 3 || node.nodeType == 4) { - textParts.push(node.data); - } - }); - iterator.detach(); - return textParts.join(""); - } - }, - - // The methods below are all non-standard. The following batch were introduced by Mozilla but have since - // been removed from Mozilla. - - compareNode: function(node) { - assertRangeValid(this); - - var parent = node.parentNode; - var nodeIndex = getNodeIndex(node); - - if (!parent) { - throw new DOMException("NOT_FOUND_ERR"); - } - - var startComparison = this.comparePoint(parent, nodeIndex), - endComparison = this.comparePoint(parent, nodeIndex + 1); - - if (startComparison < 0) { // Node starts before - return (endComparison > 0) ? n_b_a : n_b; - } else { - return (endComparison > 0) ? n_a : n_i; - } - }, - - comparePoint: function(node, offset) { - assertRangeValid(this); - assertNode(node, "HIERARCHY_REQUEST_ERR"); - assertSameDocumentOrFragment(node, this.startContainer); - - if (comparePoints(node, offset, this.startContainer, this.startOffset) < 0) { - return -1; - } else if (comparePoints(node, offset, this.endContainer, this.endOffset) > 0) { - return 1; - } - return 0; - }, - - createContextualFragment: createContextualFragment, - - toHtml: function() { - assertRangeValid(this); - var container = this.commonAncestorContainer.parentNode.cloneNode(false); - container.appendChild(this.cloneContents()); - return container.innerHTML; - }, - - // touchingIsIntersecting determines whether this method considers a node that borders a range intersects - // with it (as in WebKit) or not (as in Gecko pre-1.9, and the default) - intersectsNode: function(node, touchingIsIntersecting) { - assertRangeValid(this); - assertNode(node, "NOT_FOUND_ERR"); - if (getDocument(node) !== getRangeDocument(this)) { - return false; - } - - var parent = node.parentNode, offset = getNodeIndex(node); - assertNode(parent, "NOT_FOUND_ERR"); - - var startComparison = comparePoints(parent, offset, this.endContainer, this.endOffset), - endComparison = comparePoints(parent, offset + 1, this.startContainer, this.startOffset); - - return touchingIsIntersecting ? startComparison <= 0 && endComparison >= 0 : startComparison < 0 && endComparison > 0; - }, - - isPointInRange: function(node, offset) { - assertRangeValid(this); - assertNode(node, "HIERARCHY_REQUEST_ERR"); - assertSameDocumentOrFragment(node, this.startContainer); - - return (comparePoints(node, offset, this.startContainer, this.startOffset) >= 0) && - (comparePoints(node, offset, this.endContainer, this.endOffset) <= 0); - }, - - // The methods below are non-standard and invented by me. - - // Sharing a boundary start-to-end or end-to-start does not count as intersection. - intersectsRange: function(range) { - return rangesIntersect(this, range, false); - }, - - // Sharing a boundary start-to-end or end-to-start does count as intersection. - intersectsOrTouchesRange: function(range) { - return rangesIntersect(this, range, true); - }, - - intersection: function(range) { - if (this.intersectsRange(range)) { - var startComparison = comparePoints(this.startContainer, this.startOffset, range.startContainer, range.startOffset), - endComparison = comparePoints(this.endContainer, this.endOffset, range.endContainer, range.endOffset); - - var intersectionRange = this.cloneRange(); - if (startComparison == -1) { - intersectionRange.setStart(range.startContainer, range.startOffset); - } - if (endComparison == 1) { - intersectionRange.setEnd(range.endContainer, range.endOffset); - } - return intersectionRange; - } - return null; - }, - - union: function(range) { - if (this.intersectsOrTouchesRange(range)) { - var unionRange = this.cloneRange(); - if (comparePoints(range.startContainer, range.startOffset, this.startContainer, this.startOffset) == -1) { - unionRange.setStart(range.startContainer, range.startOffset); - } - if (comparePoints(range.endContainer, range.endOffset, this.endContainer, this.endOffset) == 1) { - unionRange.setEnd(range.endContainer, range.endOffset); - } - return unionRange; - } else { - throw new RangeException("Ranges do not intersect"); - } - }, - - containsNode: function(node, allowPartial) { - if (allowPartial) { - return this.intersectsNode(node, false); - } else { - return this.compareNode(node) == n_i; - } - }, - - containsNodeContents: function(node) { - return this.comparePoint(node, 0) >= 0 && this.comparePoint(node, getNodeLength(node)) <= 0; - }, - - containsRange: function(range) { - var intersection = this.intersection(range); - return intersection !== null && range.equals(intersection); - }, - - containsNodeText: function(node) { - var nodeRange = this.cloneRange(); - nodeRange.selectNode(node); - var textNodes = nodeRange.getNodes([3]); - if (textNodes.length > 0) { - nodeRange.setStart(textNodes[0], 0); - var lastTextNode = textNodes.pop(); - nodeRange.setEnd(lastTextNode, lastTextNode.length); - var contains = this.containsRange(nodeRange); - nodeRange.detach(); - return contains; - } else { - return this.containsNodeContents(node); - } - }, - - getNodes: function(nodeTypes, filter) { - assertRangeValid(this); - return getNodesInRange(this, nodeTypes, filter); - }, - - getDocument: function() { - return getRangeDocument(this); - }, - - collapseBefore: function(node) { - assertNotDetached(this); - - this.setEndBefore(node); - this.collapse(false); - }, - - collapseAfter: function(node) { - assertNotDetached(this); - - this.setStartAfter(node); - this.collapse(true); - }, - - getBookmark: function(containerNode) { - var doc = getRangeDocument(this); - var preSelectionRange = api.createRange(doc); - containerNode = containerNode || dom.getBody(doc); - preSelectionRange.selectNodeContents(containerNode); - var range = this.intersection(preSelectionRange); - var start = 0, end = 0; - if (range) { - preSelectionRange.setEnd(range.startContainer, range.startOffset); - start = preSelectionRange.toString().length; - end = start + range.toString().length; - preSelectionRange.detach(); - } - - return { - start: start, - end: end, - containerNode: containerNode - }; - }, - - moveToBookmark: function(bookmark) { - var containerNode = bookmark.containerNode; - var charIndex = 0; - this.setStart(containerNode, 0); - this.collapse(true); - var nodeStack = [containerNode], node, foundStart = false, stop = false; - var nextCharIndex, i, childNodes; - - while (!stop && (node = nodeStack.pop())) { - if (node.nodeType == 3) { - nextCharIndex = charIndex + node.length; - if (!foundStart && bookmark.start >= charIndex && bookmark.start <= nextCharIndex) { - this.setStart(node, bookmark.start - charIndex); - foundStart = true; - } - if (foundStart && bookmark.end >= charIndex && bookmark.end <= nextCharIndex) { - this.setEnd(node, bookmark.end - charIndex); - stop = true; - } - charIndex = nextCharIndex; - } else { - childNodes = node.childNodes; - i = childNodes.length; - while (i--) { - nodeStack.push(childNodes[i]); - } - } - } - }, - - getName: function() { - return "DomRange"; - }, - - equals: function(range) { - return Range.rangesEqual(this, range); - }, - - isValid: function() { - return isRangeValid(this); - }, - - inspect: function() { - return inspect(this); - } - }); - - function copyComparisonConstantsToObject(obj) { - obj.START_TO_START = s2s; - obj.START_TO_END = s2e; - obj.END_TO_END = e2e; - obj.END_TO_START = e2s; - - obj.NODE_BEFORE = n_b; - obj.NODE_AFTER = n_a; - obj.NODE_BEFORE_AND_AFTER = n_b_a; - obj.NODE_INSIDE = n_i; - } - - function copyComparisonConstants(constructor) { - copyComparisonConstantsToObject(constructor); - copyComparisonConstantsToObject(constructor.prototype); - } - - function createRangeContentRemover(remover, boundaryUpdater) { - return function() { - assertRangeValid(this); - - var sc = this.startContainer, so = this.startOffset, root = this.commonAncestorContainer; - - var iterator = new RangeIterator(this, true); - - // Work out where to position the range after content removal - var node, boundary; - if (sc !== root) { - node = getClosestAncestorIn(sc, root, true); - boundary = getBoundaryAfterNode(node); - sc = boundary.node; - so = boundary.offset; - } - - // Check none of the range is read-only - iterateSubtree(iterator, assertNodeNotReadOnly); - - iterator.reset(); - - // Remove the content - var returnValue = remover(iterator); - iterator.detach(); - - // Move to the new position - boundaryUpdater(this, sc, so, sc, so); - - return returnValue; - }; - } - - function createPrototypeRange(constructor, boundaryUpdater, detacher) { - function createBeforeAfterNodeSetter(isBefore, isStart) { - return function(node) { - assertNotDetached(this); - assertValidNodeType(node, beforeAfterNodeTypes); - assertValidNodeType(getRootContainer(node), rootContainerNodeTypes); - - var boundary = (isBefore ? getBoundaryBeforeNode : getBoundaryAfterNode)(node); - (isStart ? setRangeStart : setRangeEnd)(this, boundary.node, boundary.offset); - }; - } - - function setRangeStart(range, node, offset) { - var ec = range.endContainer, eo = range.endOffset; - if (node !== range.startContainer || offset !== range.startOffset) { - // Check the root containers of the range and the new boundary, and also check whether the new boundary - // is after the current end. In either case, collapse the range to the new position - if (getRootContainer(node) != getRootContainer(ec) || comparePoints(node, offset, ec, eo) == 1) { - ec = node; - eo = offset; - } - boundaryUpdater(range, node, offset, ec, eo); - } - } - - function setRangeEnd(range, node, offset) { - var sc = range.startContainer, so = range.startOffset; - if (node !== range.endContainer || offset !== range.endOffset) { - // Check the root containers of the range and the new boundary, and also check whether the new boundary - // is after the current end. In either case, collapse the range to the new position - if (getRootContainer(node) != getRootContainer(sc) || comparePoints(node, offset, sc, so) == -1) { - sc = node; - so = offset; - } - boundaryUpdater(range, sc, so, node, offset); - } - } - - // Set up inheritance - var F = function() {}; - F.prototype = api.rangePrototype; - constructor.prototype = new F(); - - util.extend(constructor.prototype, { - setStart: function(node, offset) { - assertNotDetached(this); - assertNoDocTypeNotationEntityAncestor(node, true); - assertValidOffset(node, offset); - - setRangeStart(this, node, offset); - }, - - setEnd: function(node, offset) { - assertNotDetached(this); - assertNoDocTypeNotationEntityAncestor(node, true); - assertValidOffset(node, offset); - - setRangeEnd(this, node, offset); - }, - - /** - * Convenience method to set a range's start and end boundaries. Overloaded as follows: - * - Two parameters (node, offset) creates a collapsed range at that position - * - Three parameters (node, startOffset, endOffset) creates a range contained with node starting at - * startOffset and ending at endOffset - * - Four parameters (startNode, startOffset, endNode, endOffset) creates a range starting at startOffset in - * startNode and ending at endOffset in endNode - */ - setStartAndEnd: function() { - assertNotDetached(this); - - var args = arguments; - var sc = args[0], so = args[1], ec = sc, eo = so; - - switch (args.length) { - case 3: - eo = args[2]; - break; - case 4: - ec = args[2]; - eo = args[3]; - break; - } - - boundaryUpdater(this, sc, so, ec, eo); - }, - - setBoundary: function(node, offset, isStart) { - this["set" + (isStart ? "Start" : "End")](node, offset); - }, - - setStartBefore: createBeforeAfterNodeSetter(true, true), - setStartAfter: createBeforeAfterNodeSetter(false, true), - setEndBefore: createBeforeAfterNodeSetter(true, false), - setEndAfter: createBeforeAfterNodeSetter(false, false), - - collapse: function(isStart) { - assertRangeValid(this); - if (isStart) { - boundaryUpdater(this, this.startContainer, this.startOffset, this.startContainer, this.startOffset); - } else { - boundaryUpdater(this, this.endContainer, this.endOffset, this.endContainer, this.endOffset); - } - }, - - selectNodeContents: function(node) { - assertNotDetached(this); - assertNoDocTypeNotationEntityAncestor(node, true); - - boundaryUpdater(this, node, 0, node, getNodeLength(node)); - }, - - selectNode: function(node) { - assertNotDetached(this); - assertNoDocTypeNotationEntityAncestor(node, false); - assertValidNodeType(node, beforeAfterNodeTypes); - - var start = getBoundaryBeforeNode(node), end = getBoundaryAfterNode(node); - boundaryUpdater(this, start.node, start.offset, end.node, end.offset); - }, - - extractContents: createRangeContentRemover(extractSubtree, boundaryUpdater), - - deleteContents: createRangeContentRemover(deleteSubtree, boundaryUpdater), - - canSurroundContents: function() { - assertRangeValid(this); - assertNodeNotReadOnly(this.startContainer); - assertNodeNotReadOnly(this.endContainer); - - // Check if the contents can be surrounded. Specifically, this means whether the range partially selects - // no non-text nodes. - var iterator = new RangeIterator(this, true); - var boundariesInvalid = (iterator._first && (isNonTextPartiallySelected(iterator._first, this)) || - (iterator._last && isNonTextPartiallySelected(iterator._last, this))); - iterator.detach(); - return !boundariesInvalid; - }, - - detach: function() { - detacher(this); - }, - - splitBoundaries: function() { - splitRangeBoundaries(this); - }, - - splitBoundariesPreservingPositions: function(positionsToPreserve) { - splitRangeBoundaries(this, positionsToPreserve); - }, - - normalizeBoundaries: function() { - assertRangeValid(this); - - var sc = this.startContainer, so = this.startOffset, ec = this.endContainer, eo = this.endOffset; - - var mergeForward = function(node) { - var sibling = node.nextSibling; - if (sibling && sibling.nodeType == node.nodeType) { - ec = node; - eo = node.length; - node.appendData(sibling.data); - sibling.parentNode.removeChild(sibling); - } - }; - - var mergeBackward = function(node) { - var sibling = node.previousSibling; - if (sibling && sibling.nodeType == node.nodeType) { - sc = node; - var nodeLength = node.length; - so = sibling.length; - node.insertData(0, sibling.data); - sibling.parentNode.removeChild(sibling); - if (sc == ec) { - eo += so; - ec = sc; - } else if (ec == node.parentNode) { - var nodeIndex = getNodeIndex(node); - if (eo == nodeIndex) { - ec = node; - eo = nodeLength; - } else if (eo > nodeIndex) { - eo--; - } - } - } - }; - - var normalizeStart = true; - - if (isCharacterDataNode(ec)) { - if (ec.length == eo) { - mergeForward(ec); - } - } else { - if (eo > 0) { - var endNode = ec.childNodes[eo - 1]; - if (endNode && isCharacterDataNode(endNode)) { - mergeForward(endNode); - } - } - normalizeStart = !this.collapsed; - } - - if (normalizeStart) { - if (isCharacterDataNode(sc)) { - if (so == 0) { - mergeBackward(sc); - } - } else { - if (so < sc.childNodes.length) { - var startNode = sc.childNodes[so]; - if (startNode && isCharacterDataNode(startNode)) { - mergeBackward(startNode); - } - } - } - } else { - sc = ec; - so = eo; - } - - boundaryUpdater(this, sc, so, ec, eo); - }, - - collapseToPoint: function(node, offset) { - assertNotDetached(this); - assertNoDocTypeNotationEntityAncestor(node, true); - assertValidOffset(node, offset); - this.setStartAndEnd(node, offset); - } - }); - - copyComparisonConstants(constructor); - } - - /*----------------------------------------------------------------------------------------------------------------*/ - - // Updates commonAncestorContainer and collapsed after boundary change - function updateCollapsedAndCommonAncestor(range) { - range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset); - range.commonAncestorContainer = range.collapsed ? - range.startContainer : dom.getCommonAncestor(range.startContainer, range.endContainer); - } - - function updateBoundaries(range, startContainer, startOffset, endContainer, endOffset) { - range.startContainer = startContainer; - range.startOffset = startOffset; - range.endContainer = endContainer; - range.endOffset = endOffset; - range.document = dom.getDocument(startContainer); - - updateCollapsedAndCommonAncestor(range); - } - - function detach(range) { - assertNotDetached(range); - range.startContainer = range.startOffset = range.endContainer = range.endOffset = range.document = null; - range.collapsed = range.commonAncestorContainer = null; - } - - function Range(doc) { - this.startContainer = doc; - this.startOffset = 0; - this.endContainer = doc; - this.endOffset = 0; - this.document = doc; - updateCollapsedAndCommonAncestor(this); - } - - createPrototypeRange(Range, updateBoundaries, detach); - - util.extend(Range, { - rangeProperties: rangeProperties, - RangeIterator: RangeIterator, - copyComparisonConstants: copyComparisonConstants, - createPrototypeRange: createPrototypeRange, - inspect: inspect, - getRangeDocument: getRangeDocument, - rangesEqual: function(r1, r2) { - return r1.startContainer === r2.startContainer && - r1.startOffset === r2.startOffset && - r1.endContainer === r2.endContainer && - r1.endOffset === r2.endOffset; - } - }); - - api.DomRange = Range; - api.RangeException = RangeException; -}); -rangy.createCoreModule("WrappedRange", ["DomRange"], function(api, module) { - var WrappedRange, WrappedTextRange; - var dom = api.dom; - var util = api.util; - var DomPosition = dom.DomPosition; - var DomRange = api.DomRange; - var getBody = dom.getBody; - var getContentDocument = dom.getContentDocument; - var isCharacterDataNode = dom.isCharacterDataNode; - - - /*----------------------------------------------------------------------------------------------------------------*/ - - if (api.features.implementsDomRange) { - // This is a wrapper around the browser's native DOM Range. It has two aims: - // - Provide workarounds for specific browser bugs - // - provide convenient extensions, which are inherited from Rangy's DomRange - - (function() { - var rangeProto; - var rangeProperties = DomRange.rangeProperties; - - function updateRangeProperties(range) { - var i = rangeProperties.length, prop; - while (i--) { - prop = rangeProperties[i]; - range[prop] = range.nativeRange[prop]; - } - // Fix for broken collapsed property in IE 9. - range.collapsed = (range.startContainer === range.endContainer && range.startOffset === range.endOffset); - } - - function updateNativeRange(range, startContainer, startOffset, endContainer, endOffset) { - var startMoved = (range.startContainer !== startContainer || range.startOffset != startOffset); - var endMoved = (range.endContainer !== endContainer || range.endOffset != endOffset); - var nativeRangeDifferent = !range.equals(range.nativeRange); - - // Always set both boundaries for the benefit of IE9 (see issue 35) - if (startMoved || endMoved || nativeRangeDifferent) { - range.setEnd(endContainer, endOffset); - range.setStart(startContainer, startOffset); - } - } - - function detach(range) { - range.nativeRange.detach(); - range.detached = true; - var i = rangeProperties.length; - while (i--) { - range[ rangeProperties[i] ] = null; - } - } - - var createBeforeAfterNodeSetter; - - WrappedRange = function(range) { - if (!range) { - throw module.createError("WrappedRange: Range must be specified"); - } - this.nativeRange = range; - updateRangeProperties(this); - }; - - DomRange.createPrototypeRange(WrappedRange, updateNativeRange, detach); - - rangeProto = WrappedRange.prototype; - - rangeProto.selectNode = function(node) { - this.nativeRange.selectNode(node); - updateRangeProperties(this); - }; - - rangeProto.cloneContents = function() { - return this.nativeRange.cloneContents(); - }; - - // Due to a long-standing Firefox bug that I have not been able to find a reliable way to detect, - // insertNode() is never delegated to the native range. - - rangeProto.surroundContents = function(node) { - this.nativeRange.surroundContents(node); - updateRangeProperties(this); - }; - - rangeProto.collapse = function(isStart) { - this.nativeRange.collapse(isStart); - updateRangeProperties(this); - }; - - rangeProto.cloneRange = function() { - return new WrappedRange(this.nativeRange.cloneRange()); - }; - - rangeProto.refresh = function() { - updateRangeProperties(this); - }; - - rangeProto.toString = function() { - return this.nativeRange.toString(); - }; - - // Create test range and node for feature detection - - var testTextNode = document.createTextNode("test"); - getBody(document).appendChild(testTextNode); - var range = document.createRange(); - - /*--------------------------------------------------------------------------------------------------------*/ - - // Test for Firefox 2 bug that prevents moving the start of a Range to a point after its current end and - // correct for it - - range.setStart(testTextNode, 0); - range.setEnd(testTextNode, 0); - - try { - range.setStart(testTextNode, 1); - - rangeProto.setStart = function(node, offset) { - this.nativeRange.setStart(node, offset); - updateRangeProperties(this); - }; - - rangeProto.setEnd = function(node, offset) { - this.nativeRange.setEnd(node, offset); - updateRangeProperties(this); - }; - - createBeforeAfterNodeSetter = function(name) { - return function(node) { - this.nativeRange[name](node); - updateRangeProperties(this); - }; - }; - - } catch(ex) { - - rangeProto.setStart = function(node, offset) { - try { - this.nativeRange.setStart(node, offset); - } catch (ex) { - this.nativeRange.setEnd(node, offset); - this.nativeRange.setStart(node, offset); - } - updateRangeProperties(this); - }; - - rangeProto.setEnd = function(node, offset) { - try { - this.nativeRange.setEnd(node, offset); - } catch (ex) { - this.nativeRange.setStart(node, offset); - this.nativeRange.setEnd(node, offset); - } - updateRangeProperties(this); - }; - - createBeforeAfterNodeSetter = function(name, oppositeName) { - return function(node) { - try { - this.nativeRange[name](node); - } catch (ex) { - this.nativeRange[oppositeName](node); - this.nativeRange[name](node); - } - updateRangeProperties(this); - }; - }; - } - - rangeProto.setStartBefore = createBeforeAfterNodeSetter("setStartBefore", "setEndBefore"); - rangeProto.setStartAfter = createBeforeAfterNodeSetter("setStartAfter", "setEndAfter"); - rangeProto.setEndBefore = createBeforeAfterNodeSetter("setEndBefore", "setStartBefore"); - rangeProto.setEndAfter = createBeforeAfterNodeSetter("setEndAfter", "setStartAfter"); - - /*--------------------------------------------------------------------------------------------------------*/ - - // Always use DOM4-compliant selectNodeContents implementation: it's simpler and less code than testing - // whether the native implementation can be trusted - rangeProto.selectNodeContents = function(node) { - this.setStartAndEnd(node, 0, dom.getNodeLength(node)); - }; - - /*--------------------------------------------------------------------------------------------------------*/ - - // Test for and correct WebKit bug that has the behaviour of compareBoundaryPoints round the wrong way for - // constants START_TO_END and END_TO_START: https://bugs.webkit.org/show_bug.cgi?id=20738 - - range.selectNodeContents(testTextNode); - range.setEnd(testTextNode, 3); - - var range2 = document.createRange(); - range2.selectNodeContents(testTextNode); - range2.setEnd(testTextNode, 4); - range2.setStart(testTextNode, 2); - - if (range.compareBoundaryPoints(range.START_TO_END, range2) == -1 && - range.compareBoundaryPoints(range.END_TO_START, range2) == 1) { - // This is the wrong way round, so correct for it - - rangeProto.compareBoundaryPoints = function(type, range) { - range = range.nativeRange || range; - if (type == range.START_TO_END) { - type = range.END_TO_START; - } else if (type == range.END_TO_START) { - type = range.START_TO_END; - } - return this.nativeRange.compareBoundaryPoints(type, range); - }; - } else { - rangeProto.compareBoundaryPoints = function(type, range) { - return this.nativeRange.compareBoundaryPoints(type, range.nativeRange || range); - }; - } - - /*--------------------------------------------------------------------------------------------------------*/ - - // Test for IE 9 deleteContents() and extractContents() bug and correct it. See issue 107. - - var el = document.createElementNS("http://www.w3.org/1999/xhtml", "div"); - el.innerHTML = "123"; - var textNode = el.firstChild; - var body = getBody(document); - body.appendChild(el); - - range.setStart(textNode, 1); - range.setEnd(textNode, 2); - range.deleteContents(); - - if (textNode.data == "13") { - // Behaviour is correct per DOM4 Range so wrap the browser's implementation of deleteContents() and - // extractContents() - rangeProto.deleteContents = function() { - this.nativeRange.deleteContents(); - updateRangeProperties(this); - }; - - rangeProto.extractContents = function() { - var frag = this.nativeRange.extractContents(); - updateRangeProperties(this); - return frag; - }; - } else { - } - - body.removeChild(el); - body = null; - - /*--------------------------------------------------------------------------------------------------------*/ - - // Test for existence of createContextualFragment and delegate to it if it exists - if (util.isHostMethod(range, "createContextualFragment")) { - rangeProto.createContextualFragment = function(fragmentStr) { - return this.nativeRange.createContextualFragment(fragmentStr); - }; - } - - /*--------------------------------------------------------------------------------------------------------*/ - - // Clean up - getBody(document).removeChild(testTextNode); - range.detach(); - range2.detach(); - - rangeProto.getName = function() { - return "WrappedRange"; - }; - - api.WrappedRange = WrappedRange; - - api.createNativeRange = function(doc) { - doc = getContentDocument(doc, module, "createNativeRange"); - return doc.createRange(); - }; - })(); - } - - if (api.features.implementsTextRange) { - /* - This is a workaround for a bug where IE returns the wrong container element from the TextRange's parentElement() - method. For example, in the following (where pipes denote the selection boundaries): - -
  • | a
  • b |
- - var range = document.selection.createRange(); - alert(range.parentElement().id); // Should alert "ul" but alerts "b" - - This method returns the common ancestor node of the following: - - the parentElement() of the textRange - - the parentElement() of the textRange after calling collapse(true) - - the parentElement() of the textRange after calling collapse(false) - */ - var getTextRangeContainerElement = function(textRange) { - var parentEl = textRange.parentElement(); - var range = textRange.duplicate(); - range.collapse(true); - var startEl = range.parentElement(); - range = textRange.duplicate(); - range.collapse(false); - var endEl = range.parentElement(); - var startEndContainer = (startEl == endEl) ? startEl : dom.getCommonAncestor(startEl, endEl); - - return startEndContainer == parentEl ? startEndContainer : dom.getCommonAncestor(parentEl, startEndContainer); - }; - - var textRangeIsCollapsed = function(textRange) { - return textRange.compareEndPoints("StartToEnd", textRange) == 0; - }; - - // Gets the boundary of a TextRange expressed as a node and an offset within that node. This function started out as - // an improved version of code found in Tim Cameron Ryan's IERange (http://code.google.com/p/ierange/) but has - // grown, fixing problems with line breaks in preformatted text, adding workaround for IE TextRange bugs, handling - // for inputs and images, plus optimizations. - var getTextRangeBoundaryPosition = function(textRange, wholeRangeContainerElement, isStart, isCollapsed, startInfo) { - var workingRange = textRange.duplicate(); - workingRange.collapse(isStart); - var containerElement = workingRange.parentElement(); - - // Sometimes collapsing a TextRange that's at the start of a text node can move it into the previous node, so - // check for that - if (!dom.isOrIsAncestorOf(wholeRangeContainerElement, containerElement)) { - containerElement = wholeRangeContainerElement; - } - - - // Deal with nodes that cannot "contain rich HTML markup". In practice, this means form inputs, images and - // similar. See http://msdn.microsoft.com/en-us/library/aa703950%28VS.85%29.aspx - if (!containerElement.canHaveHTML) { - var pos = new DomPosition(containerElement.parentNode, dom.getNodeIndex(containerElement)); - return { - boundaryPosition: pos, - nodeInfo: { - nodeIndex: pos.offset, - containerElement: pos.node - } - }; - } - - var workingNode = dom.getDocument(containerElement).createElementNS("http://www.w3.org/1999/xhtml", "span"); - - // Workaround for HTML5 Shiv's insane violation of document.createElementNS("http://www.w3.org/1999/xhtml", ). See Rangy issue 104 and HTML5 - // Shiv issue 64: https://github.com/aFarkas/html5shiv/issues/64 - if (workingNode.parentNode) { - workingNode.parentNode.removeChild(workingNode); - } - - var comparison, workingComparisonType = isStart ? "StartToStart" : "StartToEnd"; - var previousNode, nextNode, boundaryPosition, boundaryNode; - var start = (startInfo && startInfo.containerElement == containerElement) ? startInfo.nodeIndex : 0; - var childNodeCount = containerElement.childNodes.length; - var end = childNodeCount; - - // Check end first. Code within the loop assumes that the endth child node of the container is definitely - // after the range boundary. - var nodeIndex = end; - - while (true) { - if (nodeIndex == childNodeCount) { - containerElement.appendChild(workingNode); - } else { - containerElement.insertBefore(workingNode, containerElement.childNodes[nodeIndex]); - } - workingRange.moveToElementText(workingNode); - comparison = workingRange.compareEndPoints(workingComparisonType, textRange); - if (comparison == 0 || start == end) { - break; - } else if (comparison == -1) { - if (end == start + 1) { - // We know the endth child node is after the range boundary, so we must be done. - break; - } else { - start = nodeIndex; - } - } else { - end = (end == start + 1) ? start : nodeIndex; - } - nodeIndex = Math.floor((start + end) / 2); - containerElement.removeChild(workingNode); - } - - - // We've now reached or gone past the boundary of the text range we're interested in - // so have identified the node we want - boundaryNode = workingNode.nextSibling; - - if (comparison == -1 && boundaryNode && isCharacterDataNode(boundaryNode)) { - // This is a character data node (text, comment, cdata). The working range is collapsed at the start of the - // node containing the text range's boundary, so we move the end of the working range to the boundary point - // and measure the length of its text to get the boundary's offset within the node. - workingRange.setEndPoint(isStart ? "EndToStart" : "EndToEnd", textRange); - - var offset; - - if (/[\r\n]/.test(boundaryNode.data)) { - /* - For the particular case of a boundary within a text node containing rendered line breaks (within a
-                    element, for example), we need a slightly complicated approach to get the boundary's offset in IE. The
-                    facts:
-                    
-                    - Each line break is represented as \r in the text node's data/nodeValue properties
-                    - Each line break is represented as \r\n in the TextRange's 'text' property
-                    - The 'text' property of the TextRange does not contain trailing line breaks
-                    
-                    To get round the problem presented by the final fact above, we can use the fact that TextRange's
-                    moveStart() and moveEnd() methods return the actual number of characters moved, which is not necessarily
-                    the same as the number of characters it was instructed to move. The simplest approach is to use this to
-                    store the characters moved when moving both the start and end of the range to the start of the document
-                    body and subtracting the start offset from the end offset (the "move-negative-gazillion" method).
-                    However, this is extremely slow when the document is large and the range is near the end of it. Clearly
-                    doing the mirror image (i.e. moving the range boundaries to the end of the document) has the same
-                    problem.
-                    
-                    Another approach that works is to use moveStart() to move the start boundary of the range up to the end
-                    boundary one character at a time and incrementing a counter with the value returned by the moveStart()
-                    call. However, the check for whether the start boundary has reached the end boundary is expensive, so
-                    this method is slow (although unlike "move-negative-gazillion" is largely unaffected by the location of
-                    the range within the document).
-                    
-                    The method below is a hybrid of the two methods above. It uses the fact that a string containing the
-                    TextRange's 'text' property with each \r\n converted to a single \r character cannot be longer than the
-                    text of the TextRange, so the start of the range is moved that length initially and then a character at
-                    a time to make up for any trailing line breaks not contained in the 'text' property. This has good
-                    performance in most situations compared to the previous two methods.
-                    */
-                    var tempRange = workingRange.duplicate();
-                    var rangeLength = tempRange.text.replace(/\r\n/g, "\r").length;
-
-                    offset = tempRange.moveStart("character", rangeLength);
-                    while ( (comparison = tempRange.compareEndPoints("StartToEnd", tempRange)) == -1) {
-                        offset++;
-                        tempRange.moveStart("character", 1);
-                    }
-                } else {
-                    offset = workingRange.text.length;
-                }
-                boundaryPosition = new DomPosition(boundaryNode, offset);
-            } else {
-
-                // If the boundary immediately follows a character data node and this is the end boundary, we should favour
-                // a position within that, and likewise for a start boundary preceding a character data node
-                previousNode = (isCollapsed || !isStart) && workingNode.previousSibling;
-                nextNode = (isCollapsed || isStart) && workingNode.nextSibling;
-                if (nextNode && isCharacterDataNode(nextNode)) {
-                    boundaryPosition = new DomPosition(nextNode, 0);
-                } else if (previousNode && isCharacterDataNode(previousNode)) {
-                    boundaryPosition = new DomPosition(previousNode, previousNode.data.length);
-                } else {
-                    boundaryPosition = new DomPosition(containerElement, dom.getNodeIndex(workingNode));
-                }
-            }
-
-            // Clean up
-            workingNode.parentNode.removeChild(workingNode);
-
-            return {
-                boundaryPosition: boundaryPosition,
-                nodeInfo: {
-                    nodeIndex: nodeIndex,
-                    containerElement: containerElement
-                }
-            };
-        };
-
-        // Returns a TextRange representing the boundary of a TextRange expressed as a node and an offset within that node.
-        // This function started out as an optimized version of code found in Tim Cameron Ryan's IERange
-        // (http://code.google.com/p/ierange/)
-        var createBoundaryTextRange = function(boundaryPosition, isStart) {
-            var boundaryNode, boundaryParent, boundaryOffset = boundaryPosition.offset;
-            var doc = dom.getDocument(boundaryPosition.node);
-            var workingNode, childNodes, workingRange = getBody(doc).createTextRange();
-            var nodeIsDataNode = isCharacterDataNode(boundaryPosition.node);
-
-            if (nodeIsDataNode) {
-                boundaryNode = boundaryPosition.node;
-                boundaryParent = boundaryNode.parentNode;
-            } else {
-                childNodes = boundaryPosition.node.childNodes;
-                boundaryNode = (boundaryOffset < childNodes.length) ? childNodes[boundaryOffset] : null;
-                boundaryParent = boundaryPosition.node;
-            }
-
-            // Position the range immediately before the node containing the boundary
-            workingNode = doc.createElementNS("http://www.w3.org/1999/xhtml", "span");
-
-            // Making the working element non-empty element persuades IE to consider the TextRange boundary to be within the
-            // element rather than immediately before or after it
-            workingNode.innerHTML = "&#feff;";
-
-            // insertBefore is supposed to work like appendChild if the second parameter is null. However, a bug report
-            // for IERange suggests that it can crash the browser: http://code.google.com/p/ierange/issues/detail?id=12
-            if (boundaryNode) {
-                boundaryParent.insertBefore(workingNode, boundaryNode);
-            } else {
-                boundaryParent.appendChild(workingNode);
-            }
-
-            workingRange.moveToElementText(workingNode);
-            workingRange.collapse(!isStart);
-
-            // Clean up
-            boundaryParent.removeChild(workingNode);
-
-            // Move the working range to the text offset, if required
-            if (nodeIsDataNode) {
-                workingRange[isStart ? "moveStart" : "moveEnd"]("character", boundaryOffset);
-            }
-
-            return workingRange;
-        };
-
-        /*------------------------------------------------------------------------------------------------------------*/
-
-        // This is a wrapper around a TextRange, providing full DOM Range functionality using rangy's DomRange as a
-        // prototype
-
-        WrappedTextRange = function(textRange) {
-            this.textRange = textRange;
-            this.refresh();
-        };
-
-        WrappedTextRange.prototype = new DomRange(document);
-
-        WrappedTextRange.prototype.refresh = function() {
-            var start, end, startBoundary;
-
-            // TextRange's parentElement() method cannot be trusted. getTextRangeContainerElement() works around that.
-            var rangeContainerElement = getTextRangeContainerElement(this.textRange);
-
-            if (textRangeIsCollapsed(this.textRange)) {
-                end = start = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true,
-                    true).boundaryPosition;
-            } else {
-                startBoundary = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, true, false);
-                start = startBoundary.boundaryPosition;
-
-                // An optimization used here is that if the start and end boundaries have the same parent element, the
-                // search scope for the end boundary can be limited to exclude the portion of the element that precedes
-                // the start boundary
-                end = getTextRangeBoundaryPosition(this.textRange, rangeContainerElement, false, false,
-                    startBoundary.nodeInfo).boundaryPosition;
-            }
-
-            this.setStart(start.node, start.offset);
-            this.setEnd(end.node, end.offset);
-        };
-
-        WrappedTextRange.prototype.getName = function() {
-            return "WrappedTextRange";
-        };
-
-        DomRange.copyComparisonConstants(WrappedTextRange);
-
-        WrappedTextRange.rangeToTextRange = function(range) {
-            if (range.collapsed) {
-                return createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
-            } else {
-                var startRange = createBoundaryTextRange(new DomPosition(range.startContainer, range.startOffset), true);
-                var endRange = createBoundaryTextRange(new DomPosition(range.endContainer, range.endOffset), false);
-                var textRange = getBody( DomRange.getRangeDocument(range) ).createTextRange();
-                textRange.setEndPoint("StartToStart", startRange);
-                textRange.setEndPoint("EndToEnd", endRange);
-                return textRange;
-            }
-        };
-
-        api.WrappedTextRange = WrappedTextRange;
-
-        // IE 9 and above have both implementations and Rangy makes both available. The next few lines sets which
-        // implementation to use by default.
-        if (!api.features.implementsDomRange || api.config.preferTextRange) {
-            // Add WrappedTextRange as the Range property of the global object to allow expression like Range.END_TO_END to work
-            var globalObj = (function() { return this; })();
-            if (typeof globalObj.Range == "undefined") {
-                globalObj.Range = WrappedTextRange;
-            }
-
-            api.createNativeRange = function(doc) {
-                doc = getContentDocument(doc, module, "createNativeRange");
-                return getBody(doc).createTextRange();
-            };
-
-            api.WrappedRange = WrappedTextRange;
-        }
-    }
-
-    api.createRange = function(doc) {
-        doc = getContentDocument(doc, module, "createRange");
-        return new api.WrappedRange(api.createNativeRange(doc));
-    };
-
-    api.createRangyRange = function(doc) {
-        doc = getContentDocument(doc, module, "createRangyRange");
-        return new DomRange(doc);
-    };
-
-    api.createIframeRange = function(iframeEl) {
-        module.deprecationNotice("createIframeRange()", "createRange(iframeEl)");
-        return api.createRange(iframeEl);
-    };
-
-    api.createIframeRangyRange = function(iframeEl) {
-        module.deprecationNotice("createIframeRangyRange()", "createRangyRange(iframeEl)");
-        return api.createRangyRange(iframeEl);
-    };
-
-    api.addCreateMissingNativeApiListener(function(win) {
-        var doc = win.document;
-        if (typeof doc.createRange == "undefined") {
-            doc.createRange = function() {
-                return api.createRange(doc);
-            };
-        }
-        doc = win = null;
-    });
-});
-// This module creates a selection object wrapper that conforms as closely as possible to the Selection specification
-// in the HTML Editing spec (http://dvcs.w3.org/hg/editing/raw-file/tip/editing.html#selections)
-rangy.createCoreModule("WrappedSelection", ["DomRange", "WrappedRange"], function(api, module) {
-    api.config.checkSelectionRanges = true;
-
-    var BOOLEAN = "boolean";
-    var NUMBER = "number";
-    var dom = api.dom;
-    var util = api.util;
-    var isHostMethod = util.isHostMethod;
-    var DomRange = api.DomRange;
-    var WrappedRange = api.WrappedRange;
-    var DOMException = api.DOMException;
-    var DomPosition = dom.DomPosition;
-    var getNativeSelection;
-    var selectionIsCollapsed;
-    var features = api.features;
-    var CONTROL = "Control";
-    var getDocument = dom.getDocument;
-    var getBody = dom.getBody;
-    var rangesEqual = DomRange.rangesEqual;
-
-
-    // Utility function to support direction parameters in the API that may be a string ("backward" or "forward") or a
-    // Boolean (true for backwards).
-    function isDirectionBackward(dir) {
-        return (typeof dir == "string") ? /^backward(s)?$/i.test(dir) : !!dir;
-    }
-
-    function getWindow(win, methodName) {
-        if (!win) {
-            return window;
-        } else if (dom.isWindow(win)) {
-            return win;
-        } else if (win instanceof WrappedSelection) {
-            return win.win;
-        } else {
-            var doc = dom.getContentDocument(win, module, methodName);
-            return dom.getWindow(doc);
-        }
-    }
-
-    function getWinSelection(winParam) {
-        return getWindow(winParam, "getWinSelection").getSelection();
-    }
-
-    function getDocSelection(winParam) {
-        return getWindow(winParam, "getDocSelection").document.selection;
-    }
-    
-    function winSelectionIsBackward(sel) {
-        var backward = false;
-        if (sel.anchorNode) {
-            backward = (dom.comparePoints(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset) == 1);
-        }
-        return backward;
-    }
-
-    // Test for the Range/TextRange and Selection features required
-    // Test for ability to retrieve selection
-    var implementsWinGetSelection = isHostMethod(window, "getSelection"),
-        implementsDocSelection = util.isHostObject(document, "selection");
-
-    features.implementsWinGetSelection = implementsWinGetSelection;
-    features.implementsDocSelection = implementsDocSelection;
-
-    var useDocumentSelection = implementsDocSelection && (!implementsWinGetSelection || api.config.preferTextRange);
-
-    if (useDocumentSelection) {
-        getNativeSelection = getDocSelection;
-        api.isSelectionValid = function(winParam) {
-            var doc = getWindow(winParam, "isSelectionValid").document, nativeSel = doc.selection;
-
-            // Check whether the selection TextRange is actually contained within the correct document
-            return (nativeSel.type != "None" || getDocument(nativeSel.createRange().parentElement()) == doc);
-        };
-    } else if (implementsWinGetSelection) {
-        getNativeSelection = getWinSelection;
-        api.isSelectionValid = function() {
-            return true;
-        };
-    } else {
-        module.fail("Neither document.selection or window.getSelection() detected.");
-    }
-
-    api.getNativeSelection = getNativeSelection;
-
-    var testSelection = getNativeSelection();
-    var testRange = api.createNativeRange(document);
-    var body = getBody(document);
-
-    // Obtaining a range from a selection
-    var selectionHasAnchorAndFocus = util.areHostProperties(testSelection,
-        ["anchorNode", "focusNode", "anchorOffset", "focusOffset"]);
-
-    features.selectionHasAnchorAndFocus = selectionHasAnchorAndFocus;
-
-    // Test for existence of native selection extend() method
-    var selectionHasExtend = isHostMethod(testSelection, "extend");
-    features.selectionHasExtend = selectionHasExtend;
-    
-    // Test if rangeCount exists
-    var selectionHasRangeCount = (typeof testSelection.rangeCount == NUMBER);
-    features.selectionHasRangeCount = selectionHasRangeCount;
-
-    var selectionSupportsMultipleRanges = false;
-    var collapsedNonEditableSelectionsSupported = true;
-
-    var addRangeBackwardToNative = selectionHasExtend ?
-        function(nativeSelection, range) {
-            var doc = DomRange.getRangeDocument(range);
-            var endRange = api.createRange(doc);
-            endRange.collapseToPoint(range.endContainer, range.endOffset);
-            nativeSelection.addRange(getNativeRange(endRange));
-            nativeSelection.extend(range.startContainer, range.startOffset);
-        } : null;
-
-    if (util.areHostMethods(testSelection, ["addRange", "getRangeAt", "removeAllRanges"]) &&
-            typeof testSelection.rangeCount == NUMBER && features.implementsDomRange) {
-
-        (function() {
-            // Previously an iframe was used but this caused problems in some circumstances in IE, so tests are
-            // performed on the current document's selection. See issue 109.
-
-            // Note also that if a selection previously existed, it is wiped by these tests. This should usually be fine
-            // because initialization usually happens when the document loads, but could be a problem for a script that
-            // loads and initializes Rangy later. If anyone complains, code could be added to save and restore the
-            // selection.
-            var sel = window.getSelection();
-            if (sel) {
-                // Store the current selection
-                var originalSelectionRangeCount = sel.rangeCount;
-                var selectionHasMultipleRanges = (originalSelectionRangeCount > 1);
-                var originalSelectionRanges = [];
-                var originalSelectionBackward = winSelectionIsBackward(sel); 
-                for (var i = 0; i < originalSelectionRangeCount; ++i) {
-                    originalSelectionRanges[i] = sel.getRangeAt(i);
-                }
-                
-                // Create some test elements
-                var body = getBody(document);
-                var testEl = body.appendChild( document.createElementNS("http://www.w3.org/1999/xhtml", "div") );
-                testEl.contentEditable = "false";
-                var textNode = testEl.appendChild( document.createTextNode("\u00a0\u00a0\u00a0") );
-
-                // Test whether the native selection will allow a collapsed selection within a non-editable element
-                var r1 = document.createRange();
-
-                r1.setStart(textNode, 1);
-                r1.collapse(true);
-                sel.addRange(r1);
-                collapsedNonEditableSelectionsSupported = (sel.rangeCount == 1);
-                sel.removeAllRanges();
-
-                // Test whether the native selection is capable of supporting multiple ranges
-                if (!selectionHasMultipleRanges) {
-                    var r2 = r1.cloneRange();
-                    r1.setStart(textNode, 0);
-                    r2.setEnd(textNode, 3);
-                    r2.setStart(textNode, 2);
-                    sel.addRange(r1);
-                    sel.addRange(r2);
-
-                    selectionSupportsMultipleRanges = (sel.rangeCount == 2);
-                    r2.detach();
-                }
-
-                // Clean up
-                body.removeChild(testEl);
-                sel.removeAllRanges();
-                r1.detach();
-
-                for (i = 0; i < originalSelectionRangeCount; ++i) {
-                    if (i == 0 && originalSelectionBackward) {
-                        if (addRangeBackwardToNative) {
-                            addRangeBackwardToNative(sel, originalSelectionRanges[i]);
-                        } else {
-                            api.warn("Rangy initialization: original selection was backwards but selection has been restored forwards because browser does not support Selection.extend");
-                            sel.addRange(originalSelectionRanges[i])
-                        }
-                    } else {
-                        sel.addRange(originalSelectionRanges[i])
-                    }
-                }
-            }
-        })();
-    }
-
-    features.selectionSupportsMultipleRanges = selectionSupportsMultipleRanges;
-    features.collapsedNonEditableSelectionsSupported = collapsedNonEditableSelectionsSupported;
-
-    // ControlRanges
-    var implementsControlRange = false, testControlRange;
-
-    if (body && isHostMethod(body, "createControlRange")) {
-        testControlRange = body.createControlRange();
-        if (util.areHostProperties(testControlRange, ["item", "add"])) {
-            implementsControlRange = true;
-        }
-    }
-    features.implementsControlRange = implementsControlRange;
-
-    // Selection collapsedness
-    if (selectionHasAnchorAndFocus) {
-        selectionIsCollapsed = function(sel) {
-            return sel.anchorNode === sel.focusNode && sel.anchorOffset === sel.focusOffset;
-        };
-    } else {
-        selectionIsCollapsed = function(sel) {
-            return sel.rangeCount ? sel.getRangeAt(sel.rangeCount - 1).collapsed : false;
-        };
-    }
-
-    function updateAnchorAndFocusFromRange(sel, range, backward) {
-        var anchorPrefix = backward ? "end" : "start", focusPrefix = backward ? "start" : "end";
-        sel.anchorNode = range[anchorPrefix + "Container"];
-        sel.anchorOffset = range[anchorPrefix + "Offset"];
-        sel.focusNode = range[focusPrefix + "Container"];
-        sel.focusOffset = range[focusPrefix + "Offset"];
-    }
-
-    function updateAnchorAndFocusFromNativeSelection(sel) {
-        var nativeSel = sel.nativeSelection;
-        sel.anchorNode = nativeSel.anchorNode;
-        sel.anchorOffset = nativeSel.anchorOffset;
-        sel.focusNode = nativeSel.focusNode;
-        sel.focusOffset = nativeSel.focusOffset;
-    }
-
-    function updateEmptySelection(sel) {
-        sel.anchorNode = sel.focusNode = null;
-        sel.anchorOffset = sel.focusOffset = 0;
-        sel.rangeCount = 0;
-        sel.isCollapsed = true;
-        sel._ranges.length = 0;
-    }
-
-    function getNativeRange(range) {
-        var nativeRange;
-        if (range instanceof DomRange) {
-            nativeRange = api.createNativeRange(range.getDocument());
-            nativeRange.setEnd(range.endContainer, range.endOffset);
-            nativeRange.setStart(range.startContainer, range.startOffset);
-        } else if (range instanceof WrappedRange) {
-            nativeRange = range.nativeRange;
-        } else if (features.implementsDomRange && (range instanceof dom.getWindow(range.startContainer).Range)) {
-            nativeRange = range;
-        }
-        return nativeRange;
-    }
-
-    function rangeContainsSingleElement(rangeNodes) {
-        if (!rangeNodes.length || rangeNodes[0].nodeType != 1) {
-            return false;
-        }
-        for (var i = 1, len = rangeNodes.length; i < len; ++i) {
-            if (!dom.isAncestorOf(rangeNodes[0], rangeNodes[i])) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    function getSingleElementFromRange(range) {
-        var nodes = range.getNodes();
-        if (!rangeContainsSingleElement(nodes)) {
-            throw module.createError("getSingleElementFromRange: range " + range.inspect() + " did not consist of a single element");
-        }
-        return nodes[0];
-    }
-
-    // Simple, quick test which only needs to distinguish between a TextRange and a ControlRange
-    function isTextRange(range) {
-        return !!range && typeof range.text != "undefined";
-    }
-
-    function updateFromTextRange(sel, range) {
-        // Create a Range from the selected TextRange
-        var wrappedRange = new WrappedRange(range);
-        sel._ranges = [wrappedRange];
-
-        updateAnchorAndFocusFromRange(sel, wrappedRange, false);
-        sel.rangeCount = 1;
-        sel.isCollapsed = wrappedRange.collapsed;
-    }
-
-    function updateControlSelection(sel) {
-        // Update the wrapped selection based on what's now in the native selection
-        sel._ranges.length = 0;
-        if (sel.docSelection.type == "None") {
-            updateEmptySelection(sel);
-        } else {
-            var controlRange = sel.docSelection.createRange();
-            if (isTextRange(controlRange)) {
-                // This case (where the selection type is "Control" and calling createRange() on the selection returns
-                // a TextRange) can happen in IE 9. It happens, for example, when all elements in the selected
-                // ControlRange have been removed from the ControlRange and removed from the document.
-                updateFromTextRange(sel, controlRange);
-            } else {
-                sel.rangeCount = controlRange.length;
-                var range, doc = getDocument(controlRange.item(0));
-                for (var i = 0; i < sel.rangeCount; ++i) {
-                    range = api.createRange(doc);
-                    range.selectNode(controlRange.item(i));
-                    sel._ranges.push(range);
-                }
-                sel.isCollapsed = sel.rangeCount == 1 && sel._ranges[0].collapsed;
-                updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], false);
-            }
-        }
-    }
-
-    function addRangeToControlSelection(sel, range) {
-        var controlRange = sel.docSelection.createRange();
-        var rangeElement = getSingleElementFromRange(range);
-
-        // Create a new ControlRange containing all the elements in the selected ControlRange plus the element
-        // contained by the supplied range
-        var doc = getDocument(controlRange.item(0));
-        var newControlRange = getBody(doc).createControlRange();
-        for (var i = 0, len = controlRange.length; i < len; ++i) {
-            newControlRange.add(controlRange.item(i));
-        }
-        try {
-            newControlRange.add(rangeElement);
-        } catch (ex) {
-            throw module.createError("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)");
-        }
-        newControlRange.select();
-
-        // Update the wrapped selection based on what's now in the native selection
-        updateControlSelection(sel);
-    }
-
-    var getSelectionRangeAt;
-
-    if (isHostMethod(testSelection, "getRangeAt")) {
-        // try/catch is present because getRangeAt() must have thrown an error in some browser and some situation.
-        // Unfortunately, I didn't write a comment about the specifics and am now scared to take it out. Let that be a
-        // lesson to us all, especially me.
-        getSelectionRangeAt = function(sel, index) {
-            try {
-                return sel.getRangeAt(index);
-            } catch (ex) {
-                return null;
-            }
-        };
-    } else if (selectionHasAnchorAndFocus) {
-        getSelectionRangeAt = function(sel) {
-            var doc = getDocument(sel.anchorNode);
-            var range = api.createRange(doc);
-            range.setStartAndEnd(sel.anchorNode, sel.anchorOffset, sel.focusNode, sel.focusOffset);
-
-            // Handle the case when the selection was selected backwards (from the end to the start in the
-            // document)
-            if (range.collapsed !== this.isCollapsed) {
-                range.setStartAndEnd(sel.focusNode, sel.focusOffset, sel.anchorNode, sel.anchorOffset);
-            }
-
-            return range;
-        };
-    }
-
-    function WrappedSelection(selection, docSelection, win) {
-        this.nativeSelection = selection;
-        this.docSelection = docSelection;
-        this._ranges = [];
-        this.win = win;
-        this.refresh();
-    }
-
-    WrappedSelection.prototype = api.selectionPrototype;
-
-    function deleteProperties(sel) {
-        sel.win = sel.anchorNode = sel.focusNode = sel._ranges = null;
-        sel.rangeCount = sel.anchorOffset = sel.focusOffset = 0;
-        sel.detached = true;
-    }
-
-    var cachedRangySelections = [];
-
-    function actOnCachedSelection(win, action) {
-        var i = cachedRangySelections.length, cached, sel;
-        while (i--) {
-            cached = cachedRangySelections[i];
-            sel = cached.selection;
-            if (action == "deleteAll") {
-                deleteProperties(sel);
-            } else if (cached.win == win) {
-                if (action == "delete") {
-                    cachedRangySelections.splice(i, 1);
-                    return true;
-                } else {
-                    return sel;
-                }
-            }
-        }
-        if (action == "deleteAll") {
-            cachedRangySelections.length = 0;
-        }
-        return null;
-    }
-
-    var getSelection = function(win) {
-        // Check if the parameter is a Rangy Selection object
-        if (win && win instanceof WrappedSelection) {
-            win.refresh();
-            return win;
-        }
-
-        win = getWindow(win, "getNativeSelection");
-
-        var sel = actOnCachedSelection(win);
-        var nativeSel = getNativeSelection(win), docSel = implementsDocSelection ? getDocSelection(win) : null;
-        if (sel) {
-            sel.nativeSelection = nativeSel;
-            sel.docSelection = docSel;
-            sel.refresh();
-        } else {
-            sel = new WrappedSelection(nativeSel, docSel, win);
-            cachedRangySelections.push( { win: win, selection: sel } );
-        }
-        return sel;
-    };
-
-    api.getSelection = getSelection;
-
-    api.getIframeSelection = function(iframeEl) {
-        module.deprecationNotice("getIframeSelection()", "getSelection(iframeEl)");
-        return api.getSelection(dom.getIframeWindow(iframeEl));
-    };
-
-    var selProto = WrappedSelection.prototype;
-
-    function createControlSelection(sel, ranges) {
-        // Ensure that the selection becomes of type "Control"
-        var doc = getDocument(ranges[0].startContainer);
-        var controlRange = getBody(doc).createControlRange();
-        for (var i = 0, el, len = ranges.length; i < len; ++i) {
-            el = getSingleElementFromRange(ranges[i]);
-            try {
-                controlRange.add(el);
-            } catch (ex) {
-                throw module.createError("setRanges(): Element within one of the specified Ranges could not be added to control selection (does it have layout?)");
-            }
-        }
-        controlRange.select();
-
-        // Update the wrapped selection based on what's now in the native selection
-        updateControlSelection(sel);
-    }
-
-    // Selecting a range
-    if (!useDocumentSelection && selectionHasAnchorAndFocus && util.areHostMethods(testSelection, ["removeAllRanges", "addRange"])) {
-        selProto.removeAllRanges = function() {
-            this.nativeSelection.removeAllRanges();
-            updateEmptySelection(this);
-        };
-
-        var addRangeBackward = function(sel, range) {
-            addRangeBackwardToNative(sel.nativeSelection, range);
-            sel.refresh();
-        };
-
-        if (selectionHasRangeCount) {
-            selProto.addRange = function(range, direction) {
-                if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
-                    addRangeToControlSelection(this, range);
-                } else {
-                    if (isDirectionBackward(direction) && selectionHasExtend) {
-                        addRangeBackward(this, range);
-                    } else {
-                        var previousRangeCount;
-                        if (selectionSupportsMultipleRanges) {
-                            previousRangeCount = this.rangeCount;
-                        } else {
-                            this.removeAllRanges();
-                            previousRangeCount = 0;
-                        }
-                        // Clone the native range so that changing the selected range does not affect the selection.
-                        // This is contrary to the spec but is the only way to achieve consistency between browsers. See
-                        // issue 80.
-                        this.nativeSelection.addRange(getNativeRange(range).cloneRange());
-
-                        // Check whether adding the range was successful
-                        this.rangeCount = this.nativeSelection.rangeCount;
-
-                        if (this.rangeCount == previousRangeCount + 1) {
-                            // The range was added successfully
-
-                            // Check whether the range that we added to the selection is reflected in the last range extracted from
-                            // the selection
-                            if (api.config.checkSelectionRanges) {
-                                var nativeRange = getSelectionRangeAt(this.nativeSelection, this.rangeCount - 1);
-                                if (nativeRange && !rangesEqual(nativeRange, range)) {
-                                    // Happens in WebKit with, for example, a selection placed at the start of a text node
-                                    range = new WrappedRange(nativeRange);
-                                }
-                            }
-                            this._ranges[this.rangeCount - 1] = range;
-                            updateAnchorAndFocusFromRange(this, range, selectionIsBackward(this.nativeSelection));
-                            this.isCollapsed = selectionIsCollapsed(this);
-                        } else {
-                            // The range was not added successfully. The simplest thing is to refresh
-                            this.refresh();
-                        }
-                    }
-                }
-            };
-        } else {
-            selProto.addRange = function(range, direction) {
-                if (isDirectionBackward(direction) && selectionHasExtend) {
-                    addRangeBackward(this, range);
-                } else {
-                    this.nativeSelection.addRange(getNativeRange(range));
-                    this.refresh();
-                }
-            };
-        }
-
-        selProto.setRanges = function(ranges) {
-            if (implementsControlRange && ranges.length > 1) {
-                createControlSelection(this, ranges);
-            } else {
-                this.removeAllRanges();
-                for (var i = 0, len = ranges.length; i < len; ++i) {
-                    this.addRange(ranges[i]);
-                }
-            }
-        };
-    } else if (isHostMethod(testSelection, "empty") && isHostMethod(testRange, "select") &&
-               implementsControlRange && useDocumentSelection) {
-
-        selProto.removeAllRanges = function() {
-            // Added try/catch as fix for issue #21
-            try {
-                this.docSelection.empty();
-
-                // Check for empty() not working (issue #24)
-                if (this.docSelection.type != "None") {
-                    // Work around failure to empty a control selection by instead selecting a TextRange and then
-                    // calling empty()
-                    var doc;
-                    if (this.anchorNode) {
-                        doc = getDocument(this.anchorNode);
-                    } else if (this.docSelection.type == CONTROL) {
-                        var controlRange = this.docSelection.createRange();
-                        if (controlRange.length) {
-                            doc = getDocument( controlRange.item(0) );
-                        }
-                    }
-                    if (doc) {
-                        var textRange = getBody(doc).createTextRange();
-                        textRange.select();
-                        this.docSelection.empty();
-                    }
-                }
-            } catch(ex) {}
-            updateEmptySelection(this);
-        };
-
-        selProto.addRange = function(range) {
-            if (this.docSelection.type == CONTROL) {
-                addRangeToControlSelection(this, range);
-            } else {
-                api.WrappedTextRange.rangeToTextRange(range).select();
-                this._ranges[0] = range;
-                this.rangeCount = 1;
-                this.isCollapsed = this._ranges[0].collapsed;
-                updateAnchorAndFocusFromRange(this, range, false);
-            }
-        };
-
-        selProto.setRanges = function(ranges) {
-            this.removeAllRanges();
-            var rangeCount = ranges.length;
-            if (rangeCount > 1) {
-                createControlSelection(this, ranges);
-            } else if (rangeCount) {
-                this.addRange(ranges[0]);
-            }
-        };
-    } else {
-        module.fail("No means of selecting a Range or TextRange was found");
-        return false;
-    }
-
-    selProto.getRangeAt = function(index) {
-        if (index < 0 || index >= this.rangeCount) {
-            throw new DOMException("INDEX_SIZE_ERR");
-        } else {
-            // Clone the range to preserve selection-range independence. See issue 80.
-            return this._ranges[index].cloneRange();
-        }
-    };
-
-    var refreshSelection;
-
-    if (useDocumentSelection) {
-        refreshSelection = function(sel) {
-            var range;
-            if (api.isSelectionValid(sel.win)) {
-                range = sel.docSelection.createRange();
-            } else {
-                range = getBody(sel.win.document).createTextRange();
-                range.collapse(true);
-            }
-
-            if (sel.docSelection.type == CONTROL) {
-                updateControlSelection(sel);
-            } else if (isTextRange(range)) {
-                updateFromTextRange(sel, range);
-            } else {
-                updateEmptySelection(sel);
-            }
-        };
-    } else if (isHostMethod(testSelection, "getRangeAt") && typeof testSelection.rangeCount == NUMBER) {
-        refreshSelection = function(sel) {
-            if (implementsControlRange && implementsDocSelection && sel.docSelection.type == CONTROL) {
-                updateControlSelection(sel);
-            } else {
-                sel._ranges.length = sel.rangeCount = sel.nativeSelection.rangeCount;
-                if (sel.rangeCount) {
-                    for (var i = 0, len = sel.rangeCount; i < len; ++i) {
-                        sel._ranges[i] = new api.WrappedRange(sel.nativeSelection.getRangeAt(i));
-                    }
-                    updateAnchorAndFocusFromRange(sel, sel._ranges[sel.rangeCount - 1], selectionIsBackward(sel.nativeSelection));
-                    sel.isCollapsed = selectionIsCollapsed(sel);
-                } else {
-                    updateEmptySelection(sel);
-                }
-            }
-        };
-    } else if (selectionHasAnchorAndFocus && typeof testSelection.isCollapsed == BOOLEAN && typeof testRange.collapsed == BOOLEAN && features.implementsDomRange) {
-        refreshSelection = function(sel) {
-            var range, nativeSel = sel.nativeSelection;
-            if (nativeSel.anchorNode) {
-                range = getSelectionRangeAt(nativeSel, 0);
-                sel._ranges = [range];
-                sel.rangeCount = 1;
-                updateAnchorAndFocusFromNativeSelection(sel);
-                sel.isCollapsed = selectionIsCollapsed(sel);
-            } else {
-                updateEmptySelection(sel);
-            }
-        };
-    } else {
-        module.fail("No means of obtaining a Range or TextRange from the user's selection was found");
-        return false;
-    }
-
-    selProto.refresh = function(checkForChanges) {
-        var oldRanges = checkForChanges ? this._ranges.slice(0) : null;
-        var oldAnchorNode = this.anchorNode, oldAnchorOffset = this.anchorOffset;
-
-        refreshSelection(this);
-        if (checkForChanges) {
-            // Check the range count first
-            var i = oldRanges.length;
-            if (i != this._ranges.length) {
-                return true;
-            }
-
-            // Now check the direction. Checking the anchor position is the same is enough since we're checking all the
-            // ranges after this
-            if (this.anchorNode != oldAnchorNode || this.anchorOffset != oldAnchorOffset) {
-                return true;
-            }
-
-            // Finally, compare each range in turn
-            while (i--) {
-                if (!rangesEqual(oldRanges[i], this._ranges[i])) {
-                    return true;
-                }
-            }
-            return false;
-        }
-    };
-
-    // Removal of a single range
-    var removeRangeManually = function(sel, range) {
-        var ranges = sel.getAllRanges();
-        sel.removeAllRanges();
-        for (var i = 0, len = ranges.length; i < len; ++i) {
-            if (!rangesEqual(range, ranges[i])) {
-                sel.addRange(ranges[i]);
-            }
-        }
-        if (!sel.rangeCount) {
-            updateEmptySelection(sel);
-        }
-    };
-
-    if (implementsControlRange) {
-        selProto.removeRange = function(range) {
-            if (this.docSelection.type == CONTROL) {
-                var controlRange = this.docSelection.createRange();
-                var rangeElement = getSingleElementFromRange(range);
-
-                // Create a new ControlRange containing all the elements in the selected ControlRange minus the
-                // element contained by the supplied range
-                var doc = getDocument(controlRange.item(0));
-                var newControlRange = getBody(doc).createControlRange();
-                var el, removed = false;
-                for (var i = 0, len = controlRange.length; i < len; ++i) {
-                    el = controlRange.item(i);
-                    if (el !== rangeElement || removed) {
-                        newControlRange.add(controlRange.item(i));
-                    } else {
-                        removed = true;
-                    }
-                }
-                newControlRange.select();
-
-                // Update the wrapped selection based on what's now in the native selection
-                updateControlSelection(this);
-            } else {
-                removeRangeManually(this, range);
-            }
-        };
-    } else {
-        selProto.removeRange = function(range) {
-            removeRangeManually(this, range);
-        };
-    }
-
-    // Detecting if a selection is backward
-    var selectionIsBackward;
-    if (!useDocumentSelection && selectionHasAnchorAndFocus && features.implementsDomRange) {
-        selectionIsBackward = winSelectionIsBackward;
-
-        selProto.isBackward = function() {
-            return selectionIsBackward(this);
-        };
-    } else {
-        selectionIsBackward = selProto.isBackward = function() {
-            return false;
-        };
-    }
-
-    // Create an alias for backwards compatibility. From 1.3, everything is "backward" rather than "backwards"
-    selProto.isBackwards = selProto.isBackward;
-
-    // Selection stringifier
-    // This is conformant to the old HTML5 selections draft spec but differs from WebKit and Mozilla's implementation.
-    // The current spec does not yet define this method.
-    selProto.toString = function() {
-        var rangeTexts = [];
-        for (var i = 0, len = this.rangeCount; i < len; ++i) {
-            rangeTexts[i] = "" + this._ranges[i];
-        }
-        return rangeTexts.join("");
-    };
-
-    function assertNodeInSameDocument(sel, node) {
-        if (sel.win.document != getDocument(node)) {
-            throw new DOMException("WRONG_DOCUMENT_ERR");
-        }
-    }
-
-    // No current browser conforms fully to the spec for this method, so Rangy's own method is always used
-    selProto.collapse = function(node, offset) {
-        assertNodeInSameDocument(this, node);
-        var range = api.createRange(node);
-        range.collapseToPoint(node, offset);
-        this.setSingleRange(range);
-        this.isCollapsed = true;
-    };
-
-    selProto.collapseToStart = function() {
-        if (this.rangeCount) {
-            var range = this._ranges[0];
-            this.collapse(range.startContainer, range.startOffset);
-        } else {
-            throw new DOMException("INVALID_STATE_ERR");
-        }
-    };
-
-    selProto.collapseToEnd = function() {
-        if (this.rangeCount) {
-            var range = this._ranges[this.rangeCount - 1];
-            this.collapse(range.endContainer, range.endOffset);
-        } else {
-            throw new DOMException("INVALID_STATE_ERR");
-        }
-    };
-
-    // The spec is very specific on how selectAllChildren should be implemented so the native implementation is
-    // never used by Rangy.
-    selProto.selectAllChildren = function(node) {
-        assertNodeInSameDocument(this, node);
-        var range = api.createRange(node);
-        range.selectNodeContents(node);
-        this.setSingleRange(range);
-    };
-
-    selProto.deleteFromDocument = function() {
-        // Sepcial behaviour required for IE's control selections
-        if (implementsControlRange && implementsDocSelection && this.docSelection.type == CONTROL) {
-            var controlRange = this.docSelection.createRange();
-            var element;
-            while (controlRange.length) {
-                element = controlRange.item(0);
-                controlRange.remove(element);
-                element.parentNode.removeChild(element);
-            }
-            this.refresh();
-        } else if (this.rangeCount) {
-            var ranges = this.getAllRanges();
-            if (ranges.length) {
-                this.removeAllRanges();
-                for (var i = 0, len = ranges.length; i < len; ++i) {
-                    ranges[i].deleteContents();
-                }
-                // The spec says nothing about what the selection should contain after calling deleteContents on each
-                // range. Firefox moves the selection to where the final selected range was, so we emulate that
-                this.addRange(ranges[len - 1]);
-            }
-        }
-    };
-
-    // The following are non-standard extensions
-    selProto.eachRange = function(func, returnValue) {
-        for (var i = 0, len = this._ranges.length; i < len; ++i) {
-            if ( func( this.getRangeAt(i) ) ) {
-                return returnValue;
-            }
-        }
-    };
-
-    selProto.getAllRanges = function() {
-        var ranges = [];
-        this.eachRange(function(range) {
-            ranges.push(range);
-        });
-        return ranges;
-    };
-
-    selProto.setSingleRange = function(range, direction) {
-        this.removeAllRanges();
-        this.addRange(range, direction);
-    };
-
-    selProto.callMethodOnEachRange = function(methodName, params) {
-        var results = [];
-        this.eachRange( function(range) {
-            results.push( range[methodName].apply(range, params) );
-        } );
-        return results;
-    };
-    
-    function createStartOrEndSetter(isStart) {
-        return function(node, offset) {
-            var range;
-            if (this.rangeCount) {
-                range = this.getRangeAt(0);
-                range["set" + (isStart ? "Start" : "End")](node, offset);
-            } else {
-                range = api.createRange(this.win.document);
-                range.setStartAndEnd(node, offset);
-            }
-            this.setSingleRange(range, this.isBackward());
-        };
-    }
-
-    selProto.setStart = createStartOrEndSetter(true);
-    selProto.setEnd = createStartOrEndSetter(false);
-    
-    // Add select() method to Range prototype. Any existing selection will be removed.
-    api.rangePrototype.select = function(direction) {
-        getSelection( this.getDocument() ).setSingleRange(this, direction);
-    };
-
-    selProto.changeEachRange = function(func) {
-        var ranges = [];
-        var backward = this.isBackward();
-
-        this.eachRange(function(range) {
-            func(range);
-            ranges.push(range);
-        });
-
-        this.removeAllRanges();
-        if (backward && ranges.length == 1) {
-            this.addRange(ranges[0], "backward");
-        } else {
-            this.setRanges(ranges);
-        }
-    };
-
-    selProto.containsNode = function(node, allowPartial) {
-        return this.eachRange( function(range) {
-            return range.containsNode(node, allowPartial);
-        }, true );
-    };
-
-    selProto.getBookmark = function(containerNode) {
-        return {
-            backward: this.isBackward(),
-            rangeBookmarks: this.callMethodOnEachRange("getBookmark", [containerNode])
-        };
-    };
-
-    selProto.moveToBookmark = function(bookmark) {
-        var selRanges = [];
-        for (var i = 0, rangeBookmark, range; rangeBookmark = bookmark.rangeBookmarks[i++]; ) {
-            range = api.createRange(this.win);
-            range.moveToBookmark(rangeBookmark);
-            selRanges.push(range);
-        }
-        if (bookmark.backward) {
-            this.setSingleRange(selRanges[0], "backward");
-        } else {
-            this.setRanges(selRanges);
-        }
-    };
-
-    selProto.toHtml = function() {
-        return this.callMethodOnEachRange("toHtml").join("");
-    };
-
-    function inspect(sel) {
-        var rangeInspects = [];
-        var anchor = new DomPosition(sel.anchorNode, sel.anchorOffset);
-        var focus = new DomPosition(sel.focusNode, sel.focusOffset);
-        var name = (typeof sel.getName == "function") ? sel.getName() : "Selection";
-
-        if (typeof sel.rangeCount != "undefined") {
-            for (var i = 0, len = sel.rangeCount; i < len; ++i) {
-                rangeInspects[i] = DomRange.inspect(sel.getRangeAt(i));
-            }
-        }
-        return "[" + name + "(Ranges: " + rangeInspects.join(", ") +
-                ")(anchor: " + anchor.inspect() + ", focus: " + focus.inspect() + "]";
-    }
-
-    selProto.getName = function() {
-        return "WrappedSelection";
-    };
-
-    selProto.inspect = function() {
-        return inspect(this);
-    };
-
-    selProto.detach = function() {
-        actOnCachedSelection(this.win, "delete");
-        deleteProperties(this);
-    };
-
-    WrappedSelection.detachAll = function() {
-        actOnCachedSelection(null, "deleteAll");
-    };
-
-    WrappedSelection.inspect = inspect;
-    WrappedSelection.isDirectionBackward = isDirectionBackward;
-
-    api.Selection = WrappedSelection;
-
-    api.selectionPrototype = selProto;
-
-    api.addCreateMissingNativeApiListener(function(win) {
-        if (typeof win.getSelection == "undefined") {
-            win.getSelection = function() {
-                return getSelection(win);
-            };
-        }
-        win = null;
-    });
-});
-
-define("rangy-core", ["domReady"], (function (global) {
-    return function () {
-        var ret, fn;
-       fn = function (domReady) {
-                 var rangi = this.rangy;
-            domReady(function(){
-                rangi.init();
-            });
-            return this.rangy;
-        };
-        ret = fn.apply(global, arguments);
-        return ret || global.rangy;
-    };
-}(this)));
-
-/**
- * Highlighter module for Rangy, a cross-browser JavaScript range and selection library
- * http://code.google.com/p/rangy/
- *
- * Depends on Rangy core, TextRange and CssClassApplier modules.
- *
- * Copyright 2013, Tim Down
- * Licensed under the MIT license.
- * Version: 1.3alpha.804
- * Build date: 8 December 2013
- */
-rangy.createModule("Highlighter", ["ClassApplier"], function(api, module) {
-    var dom = api.dom;
-    var contains = dom.arrayContains;
-    var getBody = dom.getBody;
-
-    // Puts highlights in order, last in document first.
-    function compareHighlights(h1, h2) {
-        return h1.characterRange.start - h2.characterRange.start;
-    }
-
-    var forEach = [].forEach ?
-        function(arr, func) {
-            arr.forEach(func);
-        } :
-        function(arr, func) {
-            for (var i = 0, len = arr.length; i < len; ++i) {
-                func( arr[i] );
-            }
-        };
-
-    var nextHighlightId = 1;
-
-    /*----------------------------------------------------------------------------------------------------------------*/
-
-    var highlighterTypes = {};
-
-    function HighlighterType(type, converterCreator) {
-        this.type = type;
-        this.converterCreator = converterCreator;
-    }
-
-    HighlighterType.prototype.create = function() {
-        var converter = this.converterCreator();
-        converter.type = this.type;
-        return converter;
-    };
-
-    function registerHighlighterType(type, converterCreator) {
-        highlighterTypes[type] = new HighlighterType(type, converterCreator);
-    }
-
-    function getConverter(type) {
-        var highlighterType = highlighterTypes[type];
-        if (highlighterType instanceof HighlighterType) {
-            return highlighterType.create();
-        } else {
-            throw new Error("Highlighter type '" + type + "' is not valid");
-        }
-    }
-
-    api.registerHighlighterType = registerHighlighterType;
-
-    /*----------------------------------------------------------------------------------------------------------------*/
-
-    function CharacterRange(start, end) {
-        this.start = start;
-        this.end = end;
-    }
-
-    CharacterRange.prototype = {
-        intersects: function(charRange) {
-            return this.start < charRange.end && this.end > charRange.start;
-        },
-
-        union: function(charRange) {
-            return new CharacterRange(Math.min(this.start, charRange.start), Math.max(this.end, charRange.end));
-        },
-        
-        intersection: function(charRange) {
-            return new CharacterRange(Math.max(this.start, charRange.start), Math.min(this.end, charRange.end));
-        },
-
-        toString: function() {
-            return "[CharacterRange(" + this.start + ", " + this.end + ")]";
-        }
-    };
-
-    CharacterRange.fromCharacterRange = function(charRange) {
-        return new CharacterRange(charRange.start, charRange.end);
-    };
-
-    /*----------------------------------------------------------------------------------------------------------------*/
-
-    var textContentConverter = {
-        rangeToCharacterRange: function(range, containerNode) {
-            var bookmark = range.getBookmark(containerNode);
-            return new CharacterRange(bookmark.start, bookmark.end);
-        },
-
-        characterRangeToRange: function(doc, characterRange, containerNode) {
-            var range = api.createRange(doc);
-            range.moveToBookmark({
-                start: characterRange.start,
-                end: characterRange.end,
-                containerNode: containerNode
-            });
-
-            return range;
-        },
-
-        serializeSelection: function(selection, containerNode) {
-            var ranges = selection.getAllRanges(), rangeCount = ranges.length;
-            var rangeInfos = [];
-
-            var backward = rangeCount == 1 && selection.isBackward();
-
-            for (var i = 0, len = ranges.length; i < len; ++i) {
-                rangeInfos[i] = {
-                    characterRange: this.rangeToCharacterRange(ranges[i], containerNode),
-                    backward: backward
-                };
-            }
-
-            return rangeInfos;
-        },
-
-        restoreSelection: function(selection, savedSelection, containerNode) {
-            selection.removeAllRanges();
-            var doc = selection.win.document;
-            for (var i = 0, len = savedSelection.length, range, rangeInfo, characterRange; i < len; ++i) {
-                rangeInfo = savedSelection[i];
-                characterRange = rangeInfo.characterRange;
-                range = this.characterRangeToRange(doc, rangeInfo.characterRange, containerNode);
-                selection.addRange(range, rangeInfo.backward);
-            }
-        }
-    };
-
-    registerHighlighterType("textContent", function() {
-        return textContentConverter;
-    });
-
-    /*----------------------------------------------------------------------------------------------------------------*/
-
-    // Lazily load the TextRange-based converter so that the dependency is only checked when required.
-    registerHighlighterType("TextRange", (function() {
-        var converter;
-
-        return function() {
-            if (!converter) {
-                // Test that textRangeModule exists and is supported
-                var textRangeModule = api.modules.TextRange;
-                if (!textRangeModule) {
-                    throw new Error("TextRange module is missing.");
-                } else if (!textRangeModule.supported) {
-                    throw new Error("TextRange module is present but not supported.");
-                }
-
-                converter = {
-                    rangeToCharacterRange: function(range, containerNode) {
-                        return CharacterRange.fromCharacterRange( range.toCharacterRange(containerNode) );
-                    },
-
-                    characterRangeToRange: function(doc, characterRange, containerNode) {
-                        var range = api.createRange(doc);
-                        range.selectCharacters(containerNode, characterRange.start, characterRange.end);
-                        return range;
-                    },
-
-                    serializeSelection: function(selection, containerNode) {
-                        return selection.saveCharacterRanges(containerNode);
-                    },
-
-                    restoreSelection: function(selection, savedSelection, containerNode) {
-                        selection.restoreCharacterRanges(containerNode, savedSelection);
-                    }
-                };
-            }
-
-            return converter;
-        };
-    })());
-
-    /*----------------------------------------------------------------------------------------------------------------*/
-
-    function Highlight(doc, characterRange, classApplier, converter, id, containerElementId) {
-        if (id) {
-            this.id = id;
-            nextHighlightId = Math.max(nextHighlightId, id + 1);
-        } else {
-            this.id = nextHighlightId++;
-        }
-        this.characterRange = characterRange;
-        this.doc = doc;
-        this.classApplier = classApplier;
-        this.converter = converter;
-        this.containerElementId = containerElementId || null;
-        this.applied = false;
-    }
-
-    Highlight.prototype = {
-        getContainerElement: function() {
-            return this.containerElementId ? this.doc.getElementById(this.containerElementId) : getBody(this.doc);
-        },
-        
-        getRange: function() {
-            return this.converter.characterRangeToRange(this.doc, this.characterRange, this.getContainerElement());
-        },
-
-        fromRange: function(range) {
-            this.characterRange = this.converter.rangeToCharacterRange(range, this.getContainerElement());
-        },
-        
-        getText: function() {
-            return this.getRange().toString();
-        },
-
-        containsElement: function(el) {
-            return this.getRange().containsNodeContents(el.firstChild);
-        },
-
-        unapply: function() {
-            this.classApplier.undoToRange(this.getRange());
-            this.applied = false;
-        },
-
-        apply: function() {
-            this.classApplier.applyToRange(this.getRange());
-            this.applied = true;
-        },
-        
-        getHighlightElements: function() {
-            return this.classApplier.getElementsWithClassIntersectingRange(this.getRange());
-        },
-
-        toString: function() {
-            return "[Highlight(ID: " + this.id + ", class: " + this.classApplier.cssClass + ", character range: " +
-                this.characterRange.start + " - " + this.characterRange.end + ")]";
-        }
-    };
-
-    /*----------------------------------------------------------------------------------------------------------------*/
-
-    function Highlighter(doc, type) {
-        type = type || "textContent";
-        this.doc = doc || document;
-        this.classAppliers = {};
-        this.highlights = [];
-        this.converter = getConverter(type);
-    }
-
-    Highlighter.prototype = {
-        addClassApplier: function(classApplier) {
-            this.classAppliers[classApplier.cssClass] = classApplier;
-        },
-
-        getHighlightForElement: function(el) {
-            var highlights = this.highlights;
-            for (var i = 0, len = highlights.length; i < len; ++i) {
-                if (highlights[i].containsElement(el)) {
-                    return highlights[i];
-                }
-            }
-            return null;
-        },
-
-        removeHighlights: function(highlights) {
-            for (var i = 0, len = this.highlights.length, highlight; i < len; ++i) {
-                highlight = this.highlights[i];
-                if (contains(highlights, highlight)) {
-                    highlight.unapply();
-                    this.highlights.splice(i--, 1);
-                }
-            }
-        },
-
-        removeAllHighlights: function() {
-            this.removeHighlights(this.highlights);
-        },
-
-        getIntersectingHighlights: function(ranges) {
-            // Test each range against each of the highlighted ranges to see whether they overlap
-            var intersectingHighlights = [], highlights = this.highlights, converter = this.converter;
-            forEach(ranges, function(range) {
-                //var selCharRange = converter.rangeToCharacterRange(range);
-                forEach(highlights, function(highlight) {
-                    if (range.intersectsRange( highlight.getRange() ) && !contains(intersectingHighlights, highlight)) {
-                        intersectingHighlights.push(highlight);
-                    }
-                });
-            });
-
-            return intersectingHighlights;
-        },
-        
-        highlightCharacterRanges: function(className, charRanges, containerElementId) {
-            var i, len, j;
-            var highlights = this.highlights;
-            var converter = this.converter;
-            var doc = this.doc;
-            var highlightsToRemove = [];
-            var classApplier = this.classAppliers[className];
-            containerElementId = containerElementId || null;
-
-            var containerElement, containerElementRange, containerElementCharRange;
-            if (containerElementId) {
-                containerElement = this.doc.getElementById(containerElementId);
-                if (containerElement) {
-                    containerElementRange = api.createRange(this.doc);
-                    containerElementRange.selectNodeContents(containerElement);
-                    containerElementCharRange = new CharacterRange(0, containerElementRange.toString().length);
-                    containerElementRange.detach();
-                }
-            }
-
-            var charRange, highlightCharRange, merged;
-            for (i = 0, len = charRanges.length; i < len; ++i) {
-                charRange = charRanges[i];
-                merged = false;
-
-                // Restrict character range to container element, if it exists
-                if (containerElementCharRange) {
-                    charRange = charRange.intersection(containerElementCharRange);
-                }
-
-                // Check for intersection with existing highlights. For each intersection, create a new highlight
-                // which is the union of the highlight range and the selected range
-                for (j = 0; j < highlights.length; ++j) {
-                    if (containerElementId == highlights[j].containerElementId) {
-                        highlightCharRange = highlights[j].characterRange;
-
-                        if (highlightCharRange.intersects(charRange)) {
-                            // Replace the existing highlight in the list of current highlights and add it to the list for
-                            // removal
-                            highlightsToRemove.push(highlights[j]);
-                            highlights[j] = new Highlight(doc, highlightCharRange.union(charRange), classApplier, converter, null, containerElementId);
-                        }
-                    }
-                }
-
-                if (!merged) {
-                    highlights.push( new Highlight(doc, charRange, classApplier, converter, null, containerElementId) );
-                }
-            }
-            
-            // Remove the old highlights
-            forEach(highlightsToRemove, function(highlightToRemove) {
-                highlightToRemove.unapply();
-            });
-
-            // Apply new highlights
-            var newHighlights = [];
-            forEach(highlights, function(highlight) {
-                if (!highlight.applied) {
-                    highlight.apply();
-                    newHighlights.push(highlight);
-                }
-            });
-            
-            return newHighlights;
-        },
-
-        highlightRanges: function(className, ranges, containerElement) {
-            var selCharRanges = [];
-            var converter = this.converter;
-            var containerElementId = containerElement ? containerElement.id : null;
-            var containerElementRange;
-            if (containerElement) {
-                containerElementRange = api.createRange(containerElement);
-                containerElementRange.selectNodeContents(containerElement);
-            } 
-
-            forEach(ranges, function(range) {
-                var scopedRange = containerElement ? containerElementRange.intersection(range) : range;
-                selCharRanges.push( converter.rangeToCharacterRange(scopedRange, containerElement || getBody(range.getDocument())) );
-            });
-            
-            return this.highlightCharacterRanges(selCharRanges, ranges, containerElementId);
-        },
-
-        highlightSelection: function(className, selection, containerElementId) {
-            var converter = this.converter;
-            selection = selection || api.getSelection();
-            var classApplier = this.classAppliers[className];
-            var doc = selection.win.document;
-            var containerElement = containerElementId ? doc.getElementById(containerElementId) : getBody(doc);
-
-            if (!classApplier) {
-                throw new Error("No class applier found for class '" + className + "'");
-            }
-
-            // Store the existing selection as character ranges
-            var serializedSelection = converter.serializeSelection(selection, containerElement);
-
-            // Create an array of selected character ranges
-            var selCharRanges = [];
-            forEach(serializedSelection, function(rangeInfo) {
-                selCharRanges.push( CharacterRange.fromCharacterRange(rangeInfo.characterRange) );
-            });
-            
-            var newHighlights = this.highlightCharacterRanges(className, selCharRanges, containerElementId);
-
-            // Restore selection
-            converter.restoreSelection(selection, serializedSelection, containerElement);
-
-            return newHighlights;
-        },
-
-        unhighlightSelection: function(selection) {
-            selection = selection || api.getSelection();
-            var intersectingHighlights = this.getIntersectingHighlights( selection.getAllRanges() );
-            this.removeHighlights(intersectingHighlights);
-            selection.removeAllRanges();
-            return intersectingHighlights;
-        },
-
-        getHighlightsInSelection: function(selection) {
-            selection = selection || api.getSelection();
-            return this.getIntersectingHighlights(selection.getAllRanges());
-        },
-
-        selectionOverlapsHighlight: function(selection) {
-            return this.getHighlightsInSelection(selection).length > 0;
-        },
-
-        serialize: function(options) {
-            var highlights = this.highlights;
-            highlights.sort(compareHighlights);
-            var serializedHighlights = ["type:" + this.converter.type];
-
-            forEach(highlights, function(highlight) {
-                var characterRange = highlight.characterRange;
-                var parts = [
-                    characterRange.start,
-                    characterRange.end,
-                    highlight.id,
-                    highlight.classApplier.cssClass,
-                    highlight.containerElementId
-                ];
-                if (options && options.serializeHighlightText) {
-                    parts.push(highlight.getText());
-                }
-                serializedHighlights.push( parts.join("$") );
-            });
-
-            return serializedHighlights.join("|");
-        },
-
-        deserialize: function(serialized) {
-            var serializedHighlights = serialized.split("|");
-            var highlights = [];
-
-            var firstHighlight = serializedHighlights[0];
-            var regexResult;
-            var serializationType, serializationConverter, convertType = false;
-            if ( firstHighlight && (regexResult = /^type:(\w+)$/.exec(firstHighlight)) ) {
-                serializationType = regexResult[1];
-                if (serializationType != this.converter.type) {
-                    serializationConverter = getConverter(serializationType);
-                    convertType = true;
-                }
-                serializedHighlights.shift();
-            } else {
-                throw new Error("Serialized highlights are invalid.");
-            }
-            
-            var classApplier, highlight, characterRange, containerElementId, containerElement;
-
-            for (var i = serializedHighlights.length, parts; i-- > 0; ) {
-                parts = serializedHighlights[i].split("$");
-                characterRange = new CharacterRange(+parts[0], +parts[1]);
-                containerElementId = parts[4] || null;
-                containerElement = containerElementId ? this.doc.getElementById(containerElementId) : getBody(this.doc);
-
-                // Convert to the current Highlighter's type, if different from the serialization type
-                if (convertType) {
-                    characterRange = this.converter.rangeToCharacterRange(
-                        serializationConverter.characterRangeToRange(this.doc, characterRange, containerElement),
-                        containerElement
-                    );
-                }
-
-                classApplier = this.classAppliers[parts[3]];
-                highlight = new Highlight(this.doc, characterRange, classApplier, this.converter, parseInt(parts[2]), containerElementId);
-                highlight.apply();
-                highlights.push(highlight);
-            }
-            this.highlights = highlights;
-        }
-    };
-
-    api.Highlighter = Highlighter;
-
-    api.createHighlighter = function(doc, rangeCharacterOffsetConverterType) {
-        return new Highlighter(doc, rangeCharacterOffsetConverterType);
-    };
-});
-
-define("rangy-highlighter", ["rangy-core"], (function (global) {
-    return function () {
-        var ret, fn;
-        return ret || global.rangy.modules.Highlighter;
-    };
-}(this)));
-
-/**
- * Class Applier module for Rangy.
- * Adds, removes and toggles classes on Ranges and Selections
- *
- * Part of Rangy, a cross-browser JavaScript range and selection library
- * http://code.google.com/p/rangy/
- *
- * Depends on Rangy core.
- *
- * Copyright 2013, Tim Down
- * Licensed under the MIT license.
- * Version: 1.3alpha.804
- * Build date: 8 December 2013
- */
-rangy.createModule("ClassApplier", ["WrappedSelection"], function(api, module) {
-    var dom = api.dom;
-    var DomPosition = dom.DomPosition;
-    var contains = dom.arrayContains;
-
-    var defaultTagName = "span";
-
-    function each(obj, func) {
-        for (var i in obj) {
-            if (obj.hasOwnProperty(i)) {
-                if (func(i, obj[i]) === false) {
-                    return false;
-                }
-            }
-        }
-        return true;
-    }
-    
-    function trim(str) {
-        return str.replace(/^\s\s*/, "").replace(/\s\s*$/, "");
-    }
-
-    function hasClass(el, cssClass) {
-        return el.className && new RegExp("(?:^|\\s)" + cssClass + "(?:\\s|$)").test(el.className);
-    }
-
-    function addClass(el, cssClass) {
-        if (el.className) {
-            if (!hasClass(el, cssClass)) {
-                el.className += " " + cssClass;
-            }
-        } else {
-            el.className = cssClass;
-        }
-    }
-
-    var removeClass = (function() {
-        function replacer(matched, whiteSpaceBefore, whiteSpaceAfter) {
-            return (whiteSpaceBefore && whiteSpaceAfter) ? " " : "";
-        }
-
-        return function(el, cssClass) {
-            if (el.className) {
-                el.className = el.className.replace(new RegExp("(^|\\s)" + cssClass + "(\\s|$)"), replacer);
-            }
-        };
-    })();
-
-    function sortClassName(className) {
-        return className.split(/\s+/).sort().join(" ");
-    }
-
-    function getSortedClassName(el) {
-        return sortClassName(el.className);
-    }
-
-    function haveSameClasses(el1, el2) {
-        return getSortedClassName(el1) == getSortedClassName(el2);
-    }
-
-    function movePosition(position, oldParent, oldIndex, newParent, newIndex) {
-        var node = position.node, offset = position.offset;
-        var newNode = node, newOffset = offset;
-
-        if (node == newParent && offset > newIndex) {
-            ++newOffset;
-        }
-
-        if (node == oldParent && (offset == oldIndex  || offset == oldIndex + 1)) {
-            newNode = newParent;
-            newOffset += newIndex - oldIndex;
-        }
-
-        if (node == oldParent && offset > oldIndex + 1) {
-            --newOffset;
-        }
-
-        position.node = newNode;
-        position.offset = newOffset;
-    }
-    
-    function movePositionWhenRemovingNode(position, parentNode, index) {
-        if (position.node == parentNode && position.offset > index) {
-            --position.offset;
-        }
-    }
-
-    function movePreservingPositions(node, newParent, newIndex, positionsToPreserve) {
-        // For convenience, allow newIndex to be -1 to mean "insert at the end".
-        if (newIndex == -1) {
-            newIndex = newParent.childNodes.length;
-        }
-
-        var oldParent = node.parentNode;
-        var oldIndex = dom.getNodeIndex(node);
-
-        for (var i = 0, position; position = positionsToPreserve[i++]; ) {
-            movePosition(position, oldParent, oldIndex, newParent, newIndex);
-        }
-
-        // Now actually move the node.
-        if (newParent.childNodes.length == newIndex) {
-            newParent.appendChild(node);
-        } else {
-            newParent.insertBefore(node, newParent.childNodes[newIndex]);
-        }
-    }
-    
-    function removePreservingPositions(node, positionsToPreserve) {
-
-        var oldParent = node.parentNode;
-        var oldIndex = dom.getNodeIndex(node);
-
-        for (var i = 0, position; position = positionsToPreserve[i++]; ) {
-            movePositionWhenRemovingNode(position, oldParent, oldIndex);
-        }
-
-        node.parentNode.removeChild(node);
-    }
-
-    function moveChildrenPreservingPositions(node, newParent, newIndex, removeNode, positionsToPreserve) {
-        var child, children = [];
-        while ( (child = node.firstChild) ) {
-            movePreservingPositions(child, newParent, newIndex++, positionsToPreserve);
-            children.push(child);
-        }
-        if (removeNode) {
-            node.parentNode.removeChild(node);
-        }
-        return children;
-    }
-
-    function replaceWithOwnChildrenPreservingPositions(element, positionsToPreserve) {
-        return moveChildrenPreservingPositions(element, element.parentNode, dom.getNodeIndex(element), true, positionsToPreserve);
-    }
-
-    function rangeSelectsAnyText(range, textNode) {
-        var textNodeRange = range.cloneRange();
-        textNodeRange.selectNodeContents(textNode);
-
-        var intersectionRange = textNodeRange.intersection(range);
-        var text = intersectionRange ? intersectionRange.toString() : "";
-        textNodeRange.detach();
-
-        return text != "";
-    }
-
-    function getEffectiveTextNodes(range) {
-        var nodes = range.getNodes([3]);
-        
-        // Optimization as per issue 145
-        
-        // Remove non-intersecting text nodes from the start of the range
-        var start = 0, node;
-        while ( (node = nodes[start]) && !rangeSelectsAnyText(range, node) ) {
-            ++start;
-        }
-
-        // Remove non-intersecting text nodes from the start of the range
-        var end = nodes.length - 1;
-        while ( (node = nodes[end]) && !rangeSelectsAnyText(range, node) ) {
-            --end;
-        }
-        
-        return nodes.slice(start, end + 1);
-    }
-
-    function elementsHaveSameNonClassAttributes(el1, el2) {
-        if (el1.attributes.length != el2.attributes.length) return false;
-        for (var i = 0, len = el1.attributes.length, attr1, attr2, name; i < len; ++i) {
-            attr1 = el1.attributes[i];
-            name = attr1.name;
-            if (name != "class") {
-                attr2 = el2.attributes.getNamedItem(name);
-                if ( (attr1 === null) != (attr2 === null) ) return false;
-                if (attr1.specified != attr2.specified) return false;
-                if (attr1.specified && attr1.nodeValue !== attr2.nodeValue) return false;
-            }
-        }
-        return true;
-    }
-
-    function elementHasNonClassAttributes(el, exceptions) {
-        for (var i = 0, len = el.attributes.length, attrName; i < len; ++i) {
-            attrName = el.attributes[i].name;
-            if ( !(exceptions && contains(exceptions, attrName)) && el.attributes[i].specified && attrName != "class") {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    function elementHasProperties(el, props) {
-        each(props, function(p, propValue) {
-            if (typeof propValue == "object") {
-                if (!elementHasProperties(el[p], propValue)) {
-                    return false;
-                }
-            } else if (el[p] !== propValue) {
-                return false;
-            }
-        });
-        return true;
-    }
-
-    var getComputedStyleProperty = dom.getComputedStyleProperty;
-    var isEditableElement = (function() {
-        var testEl = document.createElementNS("http://www.w3.org/1999/xhtml","div");
-        return typeof testEl.isContentEditable == "boolean" ?
-            function (node) {
-                return node && node.nodeType == 1 && node.isContentEditable;
-            } :
-            function (node) {
-                if (!node || node.nodeType != 1 || node.contentEditable == "false") {
-                    return false;
-                }
-                return node.contentEditable == "true" || isEditableElement(node.parentNode);
-            };
-    })();
-
-    function isEditingHost(node) {
-        var parent;
-        return node && node.nodeType == 1
-            && (( (parent = node.parentNode) && parent.nodeType == 9 && parent.designMode == "on")
-            || (isEditableElement(node) && !isEditableElement(node.parentNode)));
-    }
-
-    function isEditable(node) {
-        return (isEditableElement(node) || (node.nodeType != 1 && isEditableElement(node.parentNode))) && !isEditingHost(node);
-    }
-
-    var inlineDisplayRegex = /^inline(-block|-table)?$/i;
-
-    function isNonInlineElement(node) {
-        return node && node.nodeType == 1 && !inlineDisplayRegex.test(getComputedStyleProperty(node, "display"));
-    }
-
-    // White space characters as defined by HTML 4 (http://www.w3.org/TR/html401/struct/text.html)
-    var htmlNonWhiteSpaceRegex = /[^\r\n\t\f \u200B]/;
-
-    function isUnrenderedWhiteSpaceNode(node) {
-        if (node.data.length == 0) {
-            return true;
-        }
-        if (htmlNonWhiteSpaceRegex.test(node.data)) {
-            return false;
-        }
-        var cssWhiteSpace = getComputedStyleProperty(node.parentNode, "whiteSpace");
-        switch (cssWhiteSpace) {
-            case "pre":
-            case "pre-wrap":
-            case "-moz-pre-wrap":
-                return false;
-            case "pre-line":
-                if (/[\r\n]/.test(node.data)) {
-                    return false;
-                }
-        }
-
-        // We now have a whitespace-only text node that may be rendered depending on its context. If it is adjacent to a
-        // non-inline element, it will not be rendered. This seems to be a good enough definition.
-        return isNonInlineElement(node.previousSibling) || isNonInlineElement(node.nextSibling);
-    }
-
-    function getRangeBoundaries(ranges) {
-        var positions = [], i, range;
-        for (i = 0; range = ranges[i++]; ) {
-            positions.push(
-                new DomPosition(range.startContainer, range.startOffset),
-                new DomPosition(range.endContainer, range.endOffset)
-            );
-        }
-        return positions;
-    }
-
-    function updateRangesFromBoundaries(ranges, positions) {
-        for (var i = 0, range, start, end, len = ranges.length; i < len; ++i) {
-            range = ranges[i];
-            start = positions[i * 2];
-            end = positions[i * 2 + 1];
-            range.setStartAndEnd(start.node, start.offset, end.node, end.offset);
-        }
-    }
-
-    function isSplitPoint(node, offset) {
-        if (dom.isCharacterDataNode(node)) {
-            if (offset == 0) {
-                return !!node.previousSibling;
-            } else if (offset == node.length) {
-                return !!node.nextSibling;
-            } else {
-                return true;
-            }
-        }
-
-        return offset > 0 && offset < node.childNodes.length;
-    }
-
-    function splitNodeAt(node, descendantNode, descendantOffset, positionsToPreserve) {
-        var newNode, parentNode;
-        var splitAtStart = (descendantOffset == 0);
-
-        if (dom.isAncestorOf(descendantNode, node)) {
-            return node;
-        }
-
-        if (dom.isCharacterDataNode(descendantNode)) {
-            var descendantIndex = dom.getNodeIndex(descendantNode);
-            if (descendantOffset == 0) {
-                descendantOffset = descendantIndex;
-            } else if (descendantOffset == descendantNode.length) {
-                descendantOffset = descendantIndex + 1;
-            } else {
-                throw module.createError("splitNodeAt() should not be called with offset in the middle of a data node ("
-                    + descendantOffset + " in " + descendantNode.data);
-            }
-            descendantNode = descendantNode.parentNode;
-        }
-
-        if (isSplitPoint(descendantNode, descendantOffset)) {
-            // descendantNode is now guaranteed not to be a text or other character node
-            newNode = descendantNode.cloneNode(false);
-            parentNode = descendantNode.parentNode;
-            if (newNode.id) {
-                newNode.removeAttribute("id");
-            }
-            var child, newChildIndex = 0;
-
-            while ( (child = descendantNode.childNodes[descendantOffset]) ) {
-                movePreservingPositions(child, newNode, newChildIndex++, positionsToPreserve);
-            }
-            movePreservingPositions(newNode, parentNode, dom.getNodeIndex(descendantNode) + 1, positionsToPreserve);
-            return (descendantNode == node) ? newNode : splitNodeAt(node, parentNode, dom.getNodeIndex(newNode), positionsToPreserve);
-        } else if (node != descendantNode) {
-            newNode = descendantNode.parentNode;
-
-            // Work out a new split point in the parent node
-            var newNodeIndex = dom.getNodeIndex(descendantNode);
-
-            if (!splitAtStart) {
-                newNodeIndex++;
-            }
-            return splitNodeAt(node, newNode, newNodeIndex, positionsToPreserve);
-        }
-        return node;
-    }
-
-    function areElementsMergeable(el1, el2) {
-        return el1.tagName == el2.tagName
-            && haveSameClasses(el1, el2)
-            && elementsHaveSameNonClassAttributes(el1, el2)
-            && getComputedStyleProperty(el1, "display") == "inline"
-            && getComputedStyleProperty(el2, "display") == "inline";
-    }
-
-    function createAdjacentMergeableTextNodeGetter(forward) {
-        var siblingPropName = forward ? "nextSibling" : "previousSibling";
-
-        return function(textNode, checkParentElement) {
-            var el = textNode.parentNode;
-            var adjacentNode = textNode[siblingPropName];
-            if (adjacentNode) {
-                // Can merge if the node's previous/next sibling is a text node
-                if (adjacentNode && adjacentNode.nodeType == 3) {
-                    return adjacentNode;
-                }
-            } else if (checkParentElement) {
-                // Compare text node parent element with its sibling
-                adjacentNode = el[siblingPropName];
-                if (adjacentNode && adjacentNode.nodeType == 1 && areElementsMergeable(el, adjacentNode)) {
-                    var adjacentNodeChild = adjacentNode[forward ? "firstChild" : "lastChild"];
-                    if (adjacentNodeChild && adjacentNodeChild.nodeType == 3) {
-                        return adjacentNodeChild;
-                    }
-                }
-            }
-            return null;
-        };
-    }
-
-    var getPreviousMergeableTextNode = createAdjacentMergeableTextNodeGetter(false),
-        getNextMergeableTextNode = createAdjacentMergeableTextNodeGetter(true);
-
-
-    function Merge(firstNode) {
-        this.isElementMerge = (firstNode.nodeType == 1);
-        this.textNodes = [];
-        var firstTextNode = this.isElementMerge ? firstNode.lastChild : firstNode;
-        if (firstTextNode) {
-            this.textNodes[0] = firstTextNode;
-        }
-    }
-
-    Merge.prototype = {
-        doMerge: function(positionsToPreserve) {
-            var textNodes = this.textNodes;
-            var firstTextNode = textNodes[0];
-            if (textNodes.length > 1) {
-                var textParts = [], combinedTextLength = 0, textNode, parent;
-                for (var i = 0, len = textNodes.length, j, position; i < len; ++i) {
-                    textNode = textNodes[i];
-                    parent = textNode.parentNode;
-                    if (i > 0) {
-                        parent.removeChild(textNode);
-                        if (!parent.hasChildNodes()) {
-                            parent.parentNode.removeChild(parent);
-                        }
-                        if (positionsToPreserve) {
-                            for (j = 0; position = positionsToPreserve[j++]; ) {
-                                // Handle case where position is inside the text node being merged into a preceding node
-                                if (position.node == textNode) {
-                                    position.node = firstTextNode;
-                                    position.offset += combinedTextLength;
-                                }
-                            }
-                        }
-                    }
-                    textParts[i] = textNode.data;
-                    combinedTextLength += textNode.data.length;
-                }
-                firstTextNode.data = textParts.join("");
-            }
-            return firstTextNode.data;
-        },
-
-        getLength: function() {
-            var i = this.textNodes.length, len = 0;
-            while (i--) {
-                len += this.textNodes[i].length;
-            }
-            return len;
-        },
-
-        toString: function() {
-            var textParts = [];
-            for (var i = 0, len = this.textNodes.length; i < len; ++i) {
-                textParts[i] = "'" + this.textNodes[i].data + "'";
-            }
-            return "[Merge(" + textParts.join(",") + ")]";
-        }
-    };
-
-    var optionProperties = ["elementTagName", "ignoreWhiteSpace", "applyToEditableOnly", "useExistingElements",
-        "removeEmptyElements", "onElementCreate"];
-
-    // TODO: Populate this with every attribute name that corresponds to a property with a different name. Really??
-    var attrNamesForProperties = {};
-
-    function ClassApplier(cssClass, options, tagNames) {
-        var normalize, i, len, propName, applier = this;
-        applier.cssClass = cssClass;
-
-        var elementPropertiesFromOptions = null, elementAttributes = {};
-
-        // Initialize from options object
-        if (typeof options == "object" && options !== null) {
-            tagNames = options.tagNames;
-            elementPropertiesFromOptions = options.elementProperties;
-            elementAttributes = options.elementAttributes;
-
-            for (i = 0; propName = optionProperties[i++]; ) {
-                if (options.hasOwnProperty(propName)) {
-                    applier[propName] = options[propName];
-                }
-            }
-            normalize = options.normalize;
-        } else {
-            normalize = options;
-        }
-
-        // Backward compatibility: the second parameter can also be a Boolean indicating to normalize after unapplying
-        applier.normalize = (typeof normalize == "undefined") ? true : normalize;
-
-        // Initialize element properties and attribute exceptions
-        applier.attrExceptions = [];
-        var el = document.createElementNS("http://www.w3.org/1999/xhtml",applier.elementTagName);
-        applier.elementProperties = applier.copyPropertiesToElement(elementPropertiesFromOptions, el, true);
-        each(elementAttributes, function(attrName) {
-            applier.attrExceptions.push(attrName);
-        });
-        applier.elementAttributes = elementAttributes;
-
-        applier.elementSortedClassName = applier.elementProperties.hasOwnProperty("className") ?
-            applier.elementProperties.className : cssClass;
-
-        // Initialize tag names
-        applier.applyToAnyTagName = false;
-        var type = typeof tagNames;
-        if (type == "string") {
-            if (tagNames == "*") {
-                applier.applyToAnyTagName = true;
-            } else {
-                applier.tagNames = trim(tagNames.toLowerCase()).split(/\s*,\s*/);
-            }
-        } else if (type == "object" && typeof tagNames.length == "number") {
-            applier.tagNames = [];
-            for (i = 0, len = tagNames.length; i < len; ++i) {
-                if (tagNames[i] == "*") {
-                    applier.applyToAnyTagName = true;
-                } else {
-                    applier.tagNames.push(tagNames[i].toLowerCase());
-                }
-            }
-        } else {
-            applier.tagNames = [applier.elementTagName];
-        }
-    }
-
-    ClassApplier.prototype = {
-        elementTagName: defaultTagName,
-        elementProperties: {},
-        elementAttributes: {},
-        ignoreWhiteSpace: true,
-        applyToEditableOnly: false,
-        useExistingElements: true,
-        removeEmptyElements: true,
-        onElementCreate: null,
-
-        copyPropertiesToElement: function(props, el, createCopy) {
-            var s, elStyle, elProps = {}, elPropsStyle, propValue, elPropValue, attrName;
-
-            for (var p in props) {
-                if (props.hasOwnProperty(p)) {
-                    propValue = props[p];
-                    elPropValue = el[p];
-
-                    // Special case for class. The copied properties object has the applier's CSS class as well as its
-                    // own to simplify checks when removing styling elements
-                    if (p == "className") {
-                        addClass(el, propValue);
-                        addClass(el, this.cssClass);
-                        el[p] = sortClassName(el[p]);
-                        if (createCopy) {
-                            elProps[p] = el[p];
-                        }
-                    }
-
-                    // Special case for style
-                    else if (p == "style") {
-                        elStyle = elPropValue;
-                        if (createCopy) {
-                            elProps[p] = elPropsStyle = {};
-                        }
-                        for (s in props[p]) {
-                            elStyle[s] = propValue[s];
-                            if (createCopy) {
-                                elPropsStyle[s] = elStyle[s];
-                            }
-                        }
-                        this.attrExceptions.push(p);
-                    } else {
-                        el[p] = propValue;
-                        // Copy the property back from the dummy element so that later comparisons to check whether
-                        // elements may be removed are checking against the right value. For example, the href property
-                        // of an element returns a fully qualified URL even if it was previously assigned a relative
-                        // URL.
-                        if (createCopy) {
-                            elProps[p] = el[p];
-
-                            // Not all properties map to identically-named attributes
-                            attrName = attrNamesForProperties.hasOwnProperty(p) ? attrNamesForProperties[p] : p;
-                            this.attrExceptions.push(attrName);
-                        }
-                    }
-                }
-            }
-
-            return createCopy ? elProps : "";
-        },
-        
-        copyAttributesToElement: function(attrs, el) {
-            for (var attrName in attrs) {
-                if (attrs.hasOwnProperty(attrName)) {
-                    el.setAttribute(attrName, attrs[attrName]);
-                }
-            }
-        },
-
-        hasClass: function(node) {
-            return node.nodeType == 1 &&
-                contains(this.tagNames, node.tagName.toLowerCase()) &&
-                hasClass(node, this.cssClass);
-        },
-
-        getSelfOrAncestorWithClass: function(node) {
-            while (node) {
-                if (this.hasClass(node)) {
-                    return node;
-                }
-                node = node.parentNode;
-            }
-            return null;
-        },
-
-        isModifiable: function(node) {
-            return !this.applyToEditableOnly || isEditable(node);
-        },
-
-        // White space adjacent to an unwrappable node can be ignored for wrapping
-        isIgnorableWhiteSpaceNode: function(node) {
-            return this.ignoreWhiteSpace && node && node.nodeType == 3 && isUnrenderedWhiteSpaceNode(node);
-        },
-
-        // Normalizes nodes after applying a CSS class to a Range.
-        postApply: function(textNodes, range, positionsToPreserve, isUndo) {
-            var firstNode = textNodes[0], lastNode = textNodes[textNodes.length - 1];
-
-            var merges = [], currentMerge;
-
-            var rangeStartNode = firstNode, rangeEndNode = lastNode;
-            var rangeStartOffset = 0, rangeEndOffset = lastNode.length;
-
-            var textNode, precedingTextNode;
-
-            // Check for every required merge and create a Merge object for each
-            for (var i = 0, len = textNodes.length; i < len; ++i) {
-                textNode = textNodes[i];
-                precedingTextNode = getPreviousMergeableTextNode(textNode, !isUndo);
-                if (precedingTextNode) {
-                    if (!currentMerge) {
-                        currentMerge = new Merge(precedingTextNode);
-                        merges.push(currentMerge);
-                    }
-                    currentMerge.textNodes.push(textNode);
-                    if (textNode === firstNode) {
-                        rangeStartNode = currentMerge.textNodes[0];
-                        rangeStartOffset = rangeStartNode.length;
-                    }
-                    if (textNode === lastNode) {
-                        rangeEndNode = currentMerge.textNodes[0];
-                        rangeEndOffset = currentMerge.getLength();
-                    }
-                } else {
-                    currentMerge = null;
-                }
-            }
-
-            // Test whether the first node after the range needs merging
-            var nextTextNode = getNextMergeableTextNode(lastNode, !isUndo);
-
-            if (nextTextNode) {
-                if (!currentMerge) {
-                    currentMerge = new Merge(lastNode);
-                    merges.push(currentMerge);
-                }
-                currentMerge.textNodes.push(nextTextNode);
-            }
-
-            // Apply the merges
-            if (merges.length) {
-                for (i = 0, len = merges.length; i < len; ++i) {
-                    merges[i].doMerge(positionsToPreserve);
-                }
-
-                // Set the range boundaries
-                range.setStartAndEnd(rangeStartNode, rangeStartOffset, rangeEndNode, rangeEndOffset);
-            }
-        },
-
-        createContainer: function(doc) {
-            var el = doc.createElementNS("http://www.w3.org/1999/xhtml",this.elementTagName);
-            this.copyPropertiesToElement(this.elementProperties, el, false);
-            this.copyAttributesToElement(this.elementAttributes, el);
-            addClass(el, this.cssClass);
-            if (this.onElementCreate) {
-                this.onElementCreate(el, this);
-            }
-            return el;
-        },
-
-        applyToTextNode: function(textNode, positionsToPreserve) {
-            var parent = textNode.parentNode;
-            if (parent.childNodes.length == 1 &&
-                    this.useExistingElements &&
-                    contains(this.tagNames, parent.tagName.toLowerCase()) &&
-                    elementHasProperties(parent, this.elementProperties)) {
-
-                addClass(parent, this.cssClass);
-            } else {
-                var el = this.createContainer(dom.getDocument(textNode));
-                textNode.parentNode.insertBefore(el, textNode);
-                el.appendChild(textNode);
-            }
-        },
-
-        isRemovable: function(el) {
-            return el.tagName.toLowerCase() == this.elementTagName
-                && getSortedClassName(el) == this.elementSortedClassName
-                && elementHasProperties(el, this.elementProperties)
-                && !elementHasNonClassAttributes(el, this.attrExceptions)
-                && this.isModifiable(el);
-        },
-
-        isEmptyContainer: function(el) {
-            var childNodeCount = el.childNodes.length;
-            return el.nodeType == 1
-                && this.isRemovable(el)
-                && (childNodeCount == 0 || (childNodeCount == 1 && this.isEmptyContainer(el.firstChild)));
-        },
-        
-        removeEmptyContainers: function(range) {
-            var applier = this;
-            var nodesToRemove = range.getNodes([1], function(el) {
-                return applier.isEmptyContainer(el);
-            });
-            
-            var rangesToPreserve = [range]
-            var positionsToPreserve = getRangeBoundaries(rangesToPreserve);
-            
-            for (var i = 0, node; node = nodesToRemove[i++]; ) {
-                removePreservingPositions(node, positionsToPreserve);
-            }
-
-            // Update the range from the preserved boundary positions
-            updateRangesFromBoundaries(rangesToPreserve, positionsToPreserve);
-        },
-
-        undoToTextNode: function(textNode, range, ancestorWithClass, positionsToPreserve) {
-            if (!range.containsNode(ancestorWithClass)) {
-                // Split out the portion of the ancestor from which we can remove the CSS class
-                //var parent = ancestorWithClass.parentNode, index = dom.getNodeIndex(ancestorWithClass);
-                var ancestorRange = range.cloneRange();
-                ancestorRange.selectNode(ancestorWithClass);
-                if (ancestorRange.isPointInRange(range.endContainer, range.endOffset)) {
-                    splitNodeAt(ancestorWithClass, range.endContainer, range.endOffset, positionsToPreserve);
-                    range.setEndAfter(ancestorWithClass);
-                }
-                if (ancestorRange.isPointInRange(range.startContainer, range.startOffset)) {
-                    ancestorWithClass = splitNodeAt(ancestorWithClass, range.startContainer, range.startOffset, positionsToPreserve);
-                }
-            }
-            if (this.isRemovable(ancestorWithClass)) {
-                replaceWithOwnChildrenPreservingPositions(ancestorWithClass, positionsToPreserve);
-            } else {
-                removeClass(ancestorWithClass, this.cssClass);
-            }
-        },
-
-        applyToRange: function(range, rangesToPreserve) {
-            rangesToPreserve = rangesToPreserve || [];
-
-            // Create an array of range boundaries to preserve
-            var positionsToPreserve = getRangeBoundaries(rangesToPreserve || []);
-            
-            range.splitBoundariesPreservingPositions(positionsToPreserve);
-
-            // Tidy up the DOM by removing empty containers 
-            if (this.removeEmptyElements) {
-                this.removeEmptyContainers(range);
-            }
-
-            var textNodes = getEffectiveTextNodes(range);
-
-            if (textNodes.length) {
-                for (var i = 0, textNode; textNode = textNodes[i++]; ) {
-                    if (!this.isIgnorableWhiteSpaceNode(textNode) && !this.getSelfOrAncestorWithClass(textNode)
-                            && this.isModifiable(textNode)) {
-                        this.applyToTextNode(textNode, positionsToPreserve);
-                    }
-                }
-                textNode = textNodes[textNodes.length - 1];
-                range.setStartAndEnd(textNodes[0], 0, textNode, textNode.length);
-                if (this.normalize) {
-                    this.postApply(textNodes, range, positionsToPreserve, false);
-                }
-
-                // Update the ranges from the preserved boundary positions
-                updateRangesFromBoundaries(rangesToPreserve, positionsToPreserve);
-            }
-        },
-
-        applyToRanges: function(ranges) {
-
-            var i = ranges.length;
-            while (i--) {
-                this.applyToRange(ranges[i], ranges);
-            }
-
-
-            return ranges;
-        },
-
-        applyToSelection: function(win) {
-            var sel = api.getSelection(win);
-            sel.setRanges( this.applyToRanges(sel.getAllRanges()) );
-        },
-
-        undoToRange: function(range, rangesToPreserve) {
-            // Create an array of range boundaries to preserve
-            rangesToPreserve = rangesToPreserve || [];
-            var positionsToPreserve = getRangeBoundaries(rangesToPreserve);
-
-
-            range.splitBoundariesPreservingPositions(positionsToPreserve);
-
-            // Tidy up the DOM by removing empty containers 
-            if (this.removeEmptyElements) {
-                this.removeEmptyContainers(range, positionsToPreserve);
-            }
-
-            var textNodes = getEffectiveTextNodes(range);
-            var textNode, ancestorWithClass;
-            var lastTextNode = textNodes[textNodes.length - 1];
-
-            if (textNodes.length) {
-                for (var i = 0, len = textNodes.length; i < len; ++i) {
-                    textNode = textNodes[i];
-                    ancestorWithClass = this.getSelfOrAncestorWithClass(textNode);
-                    if (ancestorWithClass && this.isModifiable(textNode)) {
-                        this.undoToTextNode(textNode, range, ancestorWithClass, positionsToPreserve);
-                    }
-
-                    // Ensure the range is still valid
-                    range.setStartAndEnd(textNodes[0], 0, lastTextNode, lastTextNode.length);
-                }
-
-
-                if (this.normalize) {
-                    this.postApply(textNodes, range, positionsToPreserve, true);
-                }
-
-                // Update the ranges from the preserved boundary positions
-                updateRangesFromBoundaries(rangesToPreserve, positionsToPreserve);
-            }
-        },
-
-        undoToRanges: function(ranges) {
-            // Get ranges returned in document order
-            var i = ranges.length;
-
-            while (i--) {
-                this.undoToRange(ranges[i], ranges);
-            }
-
-            return ranges;
-        },
-
-        undoToSelection: function(win) {
-            var sel = api.getSelection(win);
-            var ranges = api.getSelection(win).getAllRanges();
-            this.undoToRanges(ranges);
-            sel.setRanges(ranges);
-        },
-
-/*
-        getTextSelectedByRange: function(textNode, range) {
-            var textRange = range.cloneRange();
-            textRange.selectNodeContents(textNode);
-
-            var intersectionRange = textRange.intersection(range);
-            var text = intersectionRange ? intersectionRange.toString() : "";
-            textRange.detach();
-
-            return text;
-        },
-*/
-
-        isAppliedToRange: function(range) {
-            if (range.collapsed || range.toString() == "") {
-                return !!this.getSelfOrAncestorWithClass(range.commonAncestorContainer);
-            } else {
-                var textNodes = range.getNodes( [3] );
-                if (textNodes.length)
-                for (var i = 0, textNode; textNode = textNodes[i++]; ) {
-                    if (!this.isIgnorableWhiteSpaceNode(textNode) && rangeSelectsAnyText(range, textNode)
-                            && this.isModifiable(textNode) && !this.getSelfOrAncestorWithClass(textNode)) {
-                        return false;
-                    }
-                }
-                return true;
-            }
-        },
-
-        isAppliedToRanges: function(ranges) {
-            var i = ranges.length;
-            if (i == 0) {
-                return false;
-            }
-            while (i--) {
-                if (!this.isAppliedToRange(ranges[i])) {
-                    return false;
-                }
-            }
-            return true;
-        },
-
-        isAppliedToSelection: function(win) {
-            var sel = api.getSelection(win);
-            return this.isAppliedToRanges(sel.getAllRanges());
-        },
-
-        toggleRange: function(range) {
-            if (this.isAppliedToRange(range)) {
-                this.undoToRange(range);
-            } else {
-                this.applyToRange(range);
-            }
-        },
-
-/*
-        toggleRanges: function(ranges) {
-            if (this.isAppliedToRanges(ranges)) {
-                this.undoToRanges(ranges);
-            } else {
-                this.applyToRanges(ranges);
-            }
-        },
-*/
-
-        toggleSelection: function(win) {
-            if (this.isAppliedToSelection(win)) {
-                this.undoToSelection(win);
-            } else {
-                this.applyToSelection(win);
-            }
-        },
-        
-        getElementsWithClassIntersectingRange: function(range) {
-            var elements = [];
-            var applier = this;
-            range.getNodes([3], function(textNode) {
-                var el = applier.getSelfOrAncestorWithClass(textNode);
-                if (el && !contains(elements, el)) {
-                    elements.push(el);
-                }
-            });
-            return elements;
-        },
-
-/*
-        getElementsWithClassIntersectingSelection: function(win) {
-            var sel = api.getSelection(win);
-            var elements = [];
-            var applier = this;
-            sel.eachRange(function(range) {
-                var rangeElements = applier.getElementsWithClassIntersectingRange(range);
-                for (var i = 0, el; el = rangeElements[i++]; ) {
-                    if (!contains(elements, el)) {
-                        elements.push(el);
-                    }
-                }
-            });
-            return elements;
-        },
-*/
-
-        detach: function() {}
-    };
-
-    function createClassApplier(cssClass, options, tagNames) {
-        return new ClassApplier(cssClass, options, tagNames);
-    }
-
-    ClassApplier.util = {
-        hasClass: hasClass,
-        addClass: addClass,
-        removeClass: removeClass,
-        hasSameClasses: haveSameClasses,
-        replaceWithOwnChildren: replaceWithOwnChildrenPreservingPositions,
-        elementsHaveSameNonClassAttributes: elementsHaveSameNonClassAttributes,
-        elementHasNonClassAttributes: elementHasNonClassAttributes,
-        splitNodeAt: splitNodeAt,
-        isEditableElement: isEditableElement,
-        isEditingHost: isEditingHost,
-        isEditable: isEditable
-    };
-
-    api.CssClassApplier = api.ClassApplier = ClassApplier;
-    api.createCssClassApplier = api.createClassApplier = createClassApplier;
-});
-
-define("rangy-cssclassapplier", ["rangy-core"], (function (global) {
-    return function () {
-        var ret, fn;
-        return ret || global.rangy.modules.ClassApplier;
-    };
-}(this)));
-
-/**
- * Text range module for Rangy.
- * Text-based manipulation and searching of ranges and selections.
- *
- * Features
- *
- * - Ability to move range boundaries by character or word offsets
- * - Customizable word tokenizer
- * - Ignores text nodes inside