diff --git a/resources/viewer/hyphenate/Hyphenator.js b/resources/viewer/hyphenate/Hyphenator.js index 3a969c554d..6923518cba 100644 --- a/resources/viewer/hyphenate/Hyphenator.js +++ b/resources/viewer/hyphenate/Hyphenator.js @@ -1,115 +1,84 @@ -/** @license Hyphenator 4.2.0 - client side hyphenation for webbrowsers - * Copyright (C) 2013 Mathias Nater, Zürich (mathias at mnn dot ch) - * Project and Source hosted on http://code.google.com/p/hyphenator/ + +/** @license Hyphenator 5.1.0 - client side hyphenation for webbrowsers + * Copyright (C) 2015 Mathias Nater, Zürich (mathiasnater at gmail dot com) + * https://github.com/mnater/Hyphenator * - * This JavaScript code is free software: you can redistribute - * it and/or modify it under the terms of the GNU Lesser - * General Public License (GNU LGPL) as published by the Free Software - * Foundation, either version 3 of the License, or (at your option) - * any later version. The code is distributed WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU GPL for more details. - * - * As additional permission under GNU GPL version 3 section 7, you - * may distribute non-source (e.g., minimized or compacted) forms of - * that code without the copy of the GNU GPL normally required by - * section 4, provided you include this license notice and a URL - * through which recipients can access the Corresponding Source. - * - * - * Hyphenator.js contains code from Bram Steins hypher.js-Project: - * https://github.com/bramstein/Hypher - * - * Code from this project is marked in the source and belongs - * to the following license: - * - * Copyright (c) 2011, Bram Stein - * 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. The name of the author may not be used to endorse or promote products - * derived from this software without specific prior written permission. - * - * THIS SOFTWARE IS PROVIDED BY THE AUTHOR "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 OWNER 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. - * + * Released under the MIT license + * http://mnater.github.io/Hyphenator/LICENSE.txt */ -/* - * Comments are jsdoctoolkit formatted. See http://code.google.com/p/jsdoc-toolkit/ +/* + * Comments are jsdoc3 formatted. See http://usejsdoc.org + * Use mergeAndPack.html to get rid of the comments and to reduce the file size of this script! */ /* The following comment is for JSLint: */ -/*global window */ /*jslint browser: true */ /** - * @constructor - * @description Provides all functionality to do hyphenation, except the patterns that are loaded - * externally. - * @author Mathias Nater, mathias@mnn.ch - * @version 4.2.0 - * @namespace Holds all methods and properties + * @desc Provides all functionality to do hyphenation, except the patterns that are loaded externally + * @global + * @namespace Hyphenator + * @author Mathias Nater, + * @version 5.1.0 * @example * <script src = "Hyphenator.js" type = "text/javascript"></script> - * <script type = "text/javascript"> - *   Hyphenator.run(); - * </script> + * <script type = "text/javascript"> + * Hyphenator.run(); + * </script> */ var Hyphenator = (function (window) { 'use strict'; - /** - * @name Hyphenator-contextWindow - * @private - * @description - * contextWindow stores the window for the document to be hyphenated. - * If there are frames this will change. - * So use contextWindow instead of window! - */ + /** + * @member Hyphenator~contextWindow + * @access private + * @desc + * contextWindow stores the window for the actual document to be hyphenated. + * If there are frames this will change. + * So use contextWindow instead of window! + */ var contextWindow = window, + /** - * @name Hyphenator-supportedLangs - * @description - * A key-value object that stores supported languages and meta data. - * The key is the bcp47 code of the language and the value - * is an object containing following informations about the language: - * file: filename of the pattern file, - * script: script type of the language (e.g. 'latin' for english), this type is abbreviated by an id, - * prompt: the sentence prompted to the user, if Hyphenator.js doesn't find a language hint. - * @type {Object., Object>} - * @private - * @example - * Check if language lang is supported: + * @member {Object.} Hyphenator~supportedLangs + * @desc + * A generated key-value object that stores supported languages and meta data. + * The key is the {@link http://tools.ietf.org/rfc/bcp/bcp47.txt bcp47} code of the language and the value + * is an object of type {@link Hyphenator~supportedLangs~supportedLanguage} + * @namespace Hyphenator~supportedLangs + * @access private + * //Check if language lang is supported: * if (supportedLangs.hasOwnProperty(lang)) */ supportedLangs = (function () { + /** + * @typedef {Object} Hyphenator~supportedLangs~supportedLanguage + * @property {string} file - The name of the pattern file + * @property {number} script - The script type of the language (e.g. 'latin' for english), this type is abbreviated by an id + * @property {string} prompt - The sentence prompted to the user, if Hyphenator.js doesn't find a language hint + */ + + /** + * @lends Hyphenator~supportedLangs + */ var r = {}, + /** + * @method Hyphenator~supportedLangs~o + * @desc + * Sets a value of Hyphenator~supportedLangs + * @access protected + * @param {string} code The {@link http://tools.ietf.org/rfc/bcp/bcp47.txt bcp47} code of the language + * @param {string} file The name of the pattern file + * @param {Number} script A shortcut for a specific script: latin:0, cyrillic: 1, arabic: 2, armenian:3, bengali: 4, devangari: 5, greek: 6 + * gujarati: 7, kannada: 8, lao: 9, malayalam: 10, oriya: 11, persian: 12, punjabi: 13, tamil: 14, telugu: 15 + * @param {string} prompt The sentence prompted to the user, if Hyphenator.js doesn't find a language hint + */ o = function (code, file, script, prompt) { r[code] = {'file': file, 'script': script, 'prompt': prompt}; }; - //latin:0, cyrillic: 1, arabic: 2, armenian:3, bengali: 4, devangari: 5, greek: 6 - //gujarati: 7, kannada: 8, lao: 9, malayalam: 10, oriya: 11, persian: 12, punjabi: 13, tamil: 14, telugu: 15 - // - //(language code, file name, script, prompt) o('be', 'be.js', 1, 'Мова гэтага сайта не можа быць вызначаны аўтаматычна. Калі ласка пакажыце мову:'); o('ca', 'ca.js', 0, ''); o('cs', 'cs.js', 0, 'Jazyk této internetové stránky nebyl automaticky rozpoznán. Určete prosím její jazyk:'); @@ -149,6 +118,7 @@ var Hyphenator = (function (window) { o('ru', 'ru.js', 1, 'Язык этого сайта не может быть определен автоматически. Пожалуйста укажите язык:'); o('sk', 'sk.js', 0, ''); o('sl', 'sl.js', 0, 'Jezika te spletne strani ni bilo mogoče samodejno določiti. Prosim navedite jezik:'); + o('sr-cyrl', 'sr-cyrl.js', 1, 'Језик овог сајта није детектован аутоматски. Молим вас наведите језик:'); o('sr-latn', 'sr-latn.js', 0, 'Jezika te spletne strani ni bilo mogoče samodejno določiti. Prosim navedite jezik:'); o('sv', 'sv.js', 0, 'Spr%E5ket p%E5 den h%E4r webbplatsen kunde inte avg%F6ras automatiskt. V%E4nligen ange:'); o('ta', 'ta.js', 14, ''); @@ -162,15 +132,14 @@ var Hyphenator = (function (window) { /** - * @name Hyphenator-basePath - * @description + * @member {string} Hyphenator~basePath + * @desc * A string storing the basepath from where Hyphenator.js was loaded. - * This is used to load the patternfiles. + * This is used to load the pattern files. * The basepath is determined dynamically by searching all script-tags for Hyphenator.js - * If the path cannot be determined http://hyphenator.googlecode.com/svn/trunk/ is used as fallback. - * @type {string} - * @private - * @see Hyphenator-loadPatterns + * If the path cannot be determined {@link http://mnater.github.io/Hyphenator/} is used as fallback. + * @access private + * @see {@link Hyphenator~loadPatterns} */ basePath = (function () { var s = contextWindow.document.getElementsByTagName('script'), i = 0, p, src, t = s[i], r = ''; @@ -185,15 +154,15 @@ var Hyphenator = (function (window) { i += 1; t = s[i]; } - return !!r ? r : 'http://hyphenator.googlecode.com/svn/trunk/'; + return !!r ? r : '//mnater.github.io/Hyphenator/'; }()), /** - * @name Hyphenator-isLocal - * @private - * @description + * @member {boolean} Hyphenator~isLocal + * @access private + * @desc * isLocal is true, if Hyphenator is loaded from the same domain, as the webpage, but false, if - * it's loaded from an external source (i.e. directly from google.code) + * it's loaded from an external source (i.e. directly from github) */ isLocal = (function () { var re = false; @@ -204,132 +173,133 @@ var Hyphenator = (function (window) { }()), /** - * @name Hyphenator-documentLoaded - * @private - * @description - * documentLoaded is true, when the DOM has been loaded. This is set by runOnContentLoaded + * @member {boolean} Hyphenator~documentLoaded + * @access private + * @desc + * documentLoaded is true, when the DOM has been loaded. This is set by {@link Hyphenator~runWhenLoaded} */ documentLoaded = false, /** - * @name Hyphenator-persistentConfig - * @private - * @description + * @member {boolean} Hyphenator~persistentConfig + * @access private + * @desc * if persistentConfig is set to true (defaults to false), config options and the state of the * toggleBox are stored in DOM-storage (according to the storage-setting). So they haven't to be * set for each page. + * @default false + * @see {@link Hyphenator.config} */ persistentConfig = false, /** - * @name Hyphenator-doFrames - * @private - * @description - * switch to control if frames/iframes should be hyphenated, too + * @member {boolean} Hyphenator~doFrames + * @access private + * @desc + * switch to control if frames/iframes should be hyphenated, too. * defaults to false (frames are a bag of hurt!) + * @default false + * @see {@link Hyphenator.config} */ doFrames = false, /** - * @name Hyphenator-dontHyphenate - * @description + * @member {Object.} Hyphenator~dontHyphenate + * @desc * A key-value object containing all html-tags whose content should not be hyphenated - * @type {Object.} - * @private - * @see Hyphenator-hyphenateElement + * @access private */ - dontHyphenate = {'script': true, 'code': true, 'pre': true, 'img': true, 'br': true, 'samp': true, 'kbd': true, 'var': true, 'abbr': true, 'acronym': true, 'sub': true, 'sup': true, 'button': true, 'option': true, 'label': true, 'textarea': true, 'input': true, 'math': true, 'svg': true}, + dontHyphenate = {'video': true, 'audio': true, 'script': true, 'code': true, 'pre': true, 'img': true, 'br': true, 'samp': true, 'kbd': true, 'var': true, 'abbr': true, 'acronym': true, 'sub': true, 'sup': true, 'button': true, 'option': true, 'label': true, 'textarea': true, 'input': true, 'math': true, 'svg': true, 'style': true}, /** - * @name Hyphenator-enableCache - * @description + * @member {boolean} Hyphenator~enableCache + * @desc * A variable to set if caching is enabled or not - * @type boolean * @default true - * @private - * @see Hyphenator.config - * @see hyphenateWord + * @access private + * @see {@link Hyphenator.config} */ enableCache = true, /** - * @name Hyphenator-storageType - * @description + * @member {string} Hyphenator~storageType + * @desc * A variable to define what html5-DOM-Storage-Method is used ('none', 'local' or 'session') - * @type {string} * @default 'local' - * @private - * @see Hyphenator.config + * @access private + * @see {@link Hyphenator.config} */ storageType = 'local', /** - * @name Hyphenator-storage - * @description - * An alias to the storage-Method defined in storageType. - * Set by Hyphenator.run() - * @type {Object|undefined} + * @member {Object|undefined} Hyphenator~storage + * @desc + * An alias to the storage defined in storageType. This is set by {@link Hyphenator~createStorage}. + * Set by {@link Hyphenator.run} * @default null - * @private - * @see Hyphenator.run + * @access private + * @see {@link Hyphenator~createStorage} */ storage, /** - * @name Hyphenator-enableReducedPatternSet - * @description + * @member {boolean} Hyphenator~enableReducedPatternSet + * @desc * A variable to set if storing the used patterns is set - * @type boolean * @default false - * @private - * @see Hyphenator.config - * @see hyphenateWord - * @see Hyphenator.getRedPatternSet + * @access private + * @see {@link Hyphenator.config} + * @see {@link Hyphenator.getRedPatternSet} */ enableReducedPatternSet = false, /** - * @name Hyphenator-enableRemoteLoading - * @description + * @member {boolean} Hyphenator~enableRemoteLoading + * @desc * A variable to set if pattern files should be loaded remotely or not - * @type boolean * @default true - * @private - * @see Hyphenator.config - * @see Hyphenator-loadPatterns + * @access private + * @see {@link Hyphenator.config} */ enableRemoteLoading = true, /** - * @name Hyphenator-displayToggleBox - * @description + * @member {boolean} Hyphenator~displayToggleBox + * @desc * A variable to set if the togglebox should be displayed or not - * @type boolean * @default false - * @private - * @see Hyphenator.config - * @see Hyphenator-toggleBox + * @access private + * @see {@link Hyphenator.config} */ displayToggleBox = false, /** - * @name Hyphenator-onError - * @description + * @method Hyphenator~onError + * @desc * A function that can be called upon an error. - * @see Hyphenator.config - * @type {function(Object)} - * @private + * @see {@link Hyphenator.config} + * @access private */ onError = function (e) { window.alert("Hyphenator.js says:\n\nAn Error occurred:\n" + e.message); }, /** - * @name Hyphenator-createElem - * @description + * @method Hyphenator~onWarning + * @desc + * A function that can be called upon a warning. + * @see {@link Hyphenator.config} + * @access private + */ + onWarning = function (e) { + window.console.log(e.message); + }, + + /** + * @method Hyphenator~createElem + * @desc * A function alias to document.createElementNS or document.createElement - * @type {function(string, Object)} - * @private + * @access private */ createElem = function (tagname, context) { context = context || contextWindow; @@ -343,39 +313,44 @@ var Hyphenator = (function (window) { }, /** - * @name Hyphenator-css3 - * @description + * @member {boolean} Hyphenator~css3 + * @desc * A variable to set if css3 hyphenation should be used - * @type boolean * @default false - * @private - * @see Hyphenator.config + * @access private + * @see {@link Hyphenator.config} */ css3 = false, /** - * @name Hyphenator-css3_hsupport - * @description + * @typedef {Object} Hyphenator~css3_hsupport + * @property {boolean} support - if css3-hyphenation is supported + * @property {string} property - the css property name to access hyphen-settings (e.g. -webkit-hyphens) + * @property {Object.} supportedBrowserLangs - an object caching tested languages + * @property {function} checkLangSupport - a method that checks if the browser supports a requested language + */ + + /** + * @member {Hyphenator~css3_h9n} Hyphenator~css3_h9n + * @desc * A generated object containing information for CSS3-hyphenation support - * { - * support: boolean, - * property: , - * languages: - * } - * @type object + * This is set by {@link Hyphenator~css3_gethsupport} * @default undefined - * @private - * @see Hyphenator-css3_gethsupport + * @access private + * @see {@link Hyphenator~css3_gethsupport} + * @example + * //Check if browser supports a language + * css3_h9n.checkLangSupport(<lang>) */ css3_h9n, /** - * @name Hyphenator-css3_gethsupport - * @description - * This function sets Hyphenator-css3_h9n for the current UA + * @method Hyphenator~css3_gethsupport + * @desc + * This function sets {@link Hyphenator~css3_h9n} for the current UA * @type function - * @private - * @see Hyphenator-css3_h9n + * @access private + * @see Hyphenator~css3_h9n */ css3_gethsupport = function () { var s, @@ -417,11 +392,15 @@ var Hyphenator = (function (window) { f = function (lang) { var shadow, computedHeight, - bdy = window.document.getElementsByTagName('body')[0], + bdy, r = false; - if (supportedLangs.hasOwnProperty(lang)) { + //check if lang has already been tested + if (this.supportedBrowserLangs.hasOwnProperty(lang)) { + r = this.supportedBrowserLangs[lang]; + } else if (supportedLangs.hasOwnProperty(lang)) { //create and append shadow-test-element + bdy = window.document.getElementsByTagName('body')[0]; shadow = createElem('div', window); shadow.id = 'Hyphenator_LanguageChecker'; shadow.style.width = '5em'; @@ -439,6 +418,7 @@ var Hyphenator = (function (window) { //remove shadow element bdy.removeChild(shadow); r = (computedHeight > 12) ? true : false; + this.supportedBrowserLangs[lang] = r; } else { r = false; } @@ -448,19 +428,24 @@ var Hyphenator = (function (window) { }, r = { support: false, + supportedBrowserLangs: {}, property: '', - checkLangSupport: function () {} + checkLangSupport: {} }; if (window.getComputedStyle) { - s = contextWindow.getComputedStyle(contextWindow.document.getElementsByTagName('body')[0], null); + s = window.getComputedStyle(window.document.getElementsByTagName('body')[0], null); } else { //ancient Browsers don't support CSS3 anyway css3_h9n = r; return; } - if (s['-webkit-hyphens'] !== undefined) { + if (s.hyphens !== undefined) { + r.support = true; + r.property = 'hyphens'; + r.checkLangSupport = createLangSupportChecker('hyphens'); + } else if (s['-webkit-hyphens'] !== undefined) { r.support = true; r.property = '-webkit-hyphens'; r.checkLangSupport = createLangSupportChecker('-webkit-hyphens'); @@ -477,126 +462,125 @@ var Hyphenator = (function (window) { }, /** - * @name Hyphenator-hyphenateClass - * @description + * @member {string} Hyphenator~hyphenateClass + * @desc * A string containing the css-class-name for the hyphenate class - * @type {string} * @default 'hyphenate' - * @private + * @access private * @example * <p class = "hyphenate">Text</p> - * @see Hyphenator.config + * @see {@link Hyphenator.config} */ hyphenateClass = 'hyphenate', /** - * @name Hyphenator-classPrefix - * @description + * @member {string} Hyphenator~urlHyphenateClass + * @desc + * A string containing the css-class-name for the urlhyphenate class + * @default 'urlhyphenate' + * @access private + * @example + * <p class = "urlhyphenate">Text</p> + * @see {@link Hyphenator.config} + */ + urlHyphenateClass = 'urlhyphenate', + + /** + * @member {string} Hyphenator~classPrefix + * @desc * A string containing a unique className prefix to be used * whenever Hyphenator sets a CSS-class - * @type {string} - * @private + * @access private */ classPrefix = 'Hyphenator' + Math.round(Math.random() * 1000), /** - * @name Hyphenator-hideClass - * @description + * @member {string} Hyphenator~hideClass + * @desc * The name of the class that hides elements - * @type {string} - * @private + * @access private */ hideClass = classPrefix + 'hide', /** - * @name Hyphenator-hideClassRegExp - * @description + * @member {RegExp} Hyphenator~hideClassRegExp + * @desc * RegExp to remove hideClass from a list of classes - * @type {RegExp} - * @private + * @access private */ hideClassRegExp = new RegExp("\\s?\\b" + hideClass + "\\b", "g"), /** - * @name Hyphenator-hideClass - * @description + * @member {string} Hyphenator~hideClass + * @desc * The name of the class that unhides elements - * @type {string} - * @private + * @access private */ unhideClass = classPrefix + 'unhide', /** - * @name Hyphenator-hideClassRegExp - * @description + * @member {RegExp} Hyphenator~hideClassRegExp + * @desc * RegExp to remove unhideClass from a list of classes - * @type {RegExp} - * @private + * @access private */ unhideClassRegExp = new RegExp("\\s?\\b" + unhideClass + "\\b", "g"), /** - * @name Hyphenator-css3hyphenateClass - * @description + * @member {string} Hyphenator~css3hyphenateClass + * @desc * The name of the class that hyphenates elements with css3 - * @type {string} - * @private + * @access private */ css3hyphenateClass = classPrefix + 'css3hyphenate', /** - * @name Hyphenator-css3hyphenateClass - * @description + * @member {CSSEdit} Hyphenator~css3hyphenateClass + * @desc * The var where CSSEdit class is stored - * @type {Object} - * @private + * @access private */ css3hyphenateClassHandle, /** - * @name Hyphenator-dontHyphenateClass - * @description + * @member {string} Hyphenator~dontHyphenateClass + * @desc * A string containing the css-class-name for elements that should not be hyphenated - * @type {string} * @default 'donthyphenate' - * @private + * @access private * @example * <p class = "donthyphenate">Text</p> - * @see Hyphenator.config + * @see {@link Hyphenator.config} */ dontHyphenateClass = 'donthyphenate', /** - * @name Hyphenator-min - * @description + * @member {number} Hyphenator~min + * @desc * A number wich indicates the minimal length of words to hyphenate. - * @type {number} * @default 6 - * @private - * @see Hyphenator.config + * @access private + * @see {@link Hyphenator.config} */ min = 6, /** - * @name Hyphenator-orphanControl - * @description + * @member {number} Hyphenator~orphanControl + * @desc * Control how the last words of a line are handled: * level 1 (default): last word is hyphenated * level 2: last word is not hyphenated * level 3: last word is not hyphenated and last space is non breaking - * @type {number} * @default 1 - * @private + * @access private */ orphanControl = 1, /** - * @name Hyphenator-isBookmarklet - * @description - * Indicates if Hyphanetor runs as bookmarklet or not. - * @type boolean - * @default false - * @private + * @member {boolean} Hyphenator~isBookmarklet + * @desc + * True if Hyphanetor runs as bookmarklet. + * @access private */ isBookmarklet = (function () { var loc = null, @@ -615,56 +599,146 @@ var Hyphenator = (function (window) { }()), /** - * @name Hyphenator-mainLanguage - * @description - * The general language of the document. In contrast to {@link Hyphenator-defaultLanguage}, + * @member {string|null} Hyphenator~mainLanguage + * @desc + * The general language of the document. In contrast to {@link Hyphenator~defaultLanguage}, * mainLanguage is defined by the client (i.e. by the html or by a prompt). - * @type {string|null} - * @private - * @see Hyphenator-autoSetMainLanguage + * @access private + * @see {@link Hyphenator~autoSetMainLanguage} */ mainLanguage = null, /** - * @name Hyphenator-defaultLanguage - * @description + * @member {string|null} Hyphenator~defaultLanguage + * @desc * The language defined by the developper. This language setting is defined by a config option. * It is overwritten by any html-lang-attribute and only taken in count, when no such attribute can * be found (i.e. just before the prompt). - * @type {string|null} - * @private - * @see Hyphenator-autoSetMainLanguage + * @access private + * @see {@link Hyphenator.config} + * @see {@link Hyphenator~autoSetMainLanguage} */ defaultLanguage = '', - /** - * @name Hyphenator-elements - * @description - * An object holding all elements that have to be hyphenated. This var is filled by - * {@link Hyphenator-gatherDocumentInfos} - * @type {Array} - * @private + * @member {ElementCollection} Hyphenator~elements + * @desc + * A class representing all elements (of type Element) that have to be hyphenated. This var is filled by + * {@link Hyphenator~gatherDocumentInfos} + * @access private */ elements = (function () { + /** + * @constructor Hyphenator~elements~ElementCollection~Element + * @desc represents a DOM Element with additional information + * @access private + */ var Element = function (element) { + /** + * @member {Object} Hyphenator~elements~ElementCollection~Element~element + * @desc A DOM Element + * @access protected + */ this.element = element; + /** + * @member {boolean} Hyphenator~elements~ElementCollection~Element~hyphenated + * @desc Marks if the element has been hyphenated + * @access protected + */ this.hyphenated = false; - this.treated = false; //collected but not hyphenated (dohyphenation is off) + /** + * @member {boolean} Hyphenator~elements~ElementCollection~Element~treated + * @desc Marks if information of the element has been collected but not hyphenated (e.g. dohyphenation is off) + * @access protected + */ + this.treated = false; }, + /** + * @constructor Hyphenator~elements~ElementCollection + * @desc A collection of Elements to be hyphenated + * @access protected + */ ElementCollection = function () { + /** + * @member {number} Hyphenator~elements~ElementCollection~count + * @desc The Number of collected Elements + * @access protected + */ this.count = 0; + /** + * @member {number} Hyphenator~elements~ElementCollection~hyCount + * @desc The Number of hyphenated Elements + * @access protected + */ this.hyCount = 0; + /** + * @member {Object.>} Hyphenator~elements~ElementCollection~list + * @desc The collection of elements, where the key is a language code and the value is an array of elements + * @access protected + */ this.list = {}; }; + /** + * @member {Object} Hyphenator~elements~ElementCollection.prototype + * @augments Hyphenator~elements~ElementCollection + * @access protected + */ ElementCollection.prototype = { + /** + * @method Hyphenator~elements~ElementCollection.prototype~add + * @augments Hyphenator~elements~ElementCollection + * @access protected + * @desc adds a DOM element to the collection + * @param {Object} el - The DOM element + * @param {string} lang - The language of the element + */ add: function (el, lang) { + var elo = new Element(el); if (!this.list.hasOwnProperty(lang)) { this.list[lang] = []; } - this.list[lang].push(new Element(el)); + this.list[lang].push(elo); this.count += 1; + return elo; }, + + /** + * @method Hyphenator~elements~ElementCollection.prototype~remove + * @augments Hyphenator~elements~ElementCollection + * @access protected + * @desc removes a DOM element from the collection + * @param {Object} el - The DOM element + */ + remove: function (el) { + var lang, i, e, l; + for (lang in this.list) { + if (this.list.hasOwnProperty(lang)) { + for (i = 0; i < this.list[lang].length; i += 1) { + if (this.list[lang][i].element === el) { + e = i; + l = lang; + break; + } + } + } + } + this.list[l].splice(e, 1); + this.count -= 1; + this.hyCount -= 1; + }, + /** + * @callback Hyphenator~elements~ElementCollection.prototype~each~callback fn - The callback that is executed for each element + * @param {string} [k] The key (i.e. language) of the collection + * @param {Hyphenator~elements~ElementCollection~Element} element + */ + + /** + * @method Hyphenator~elements~ElementCollection.prototype~each + * @augments Hyphenator~elements~ElementCollection + * @access protected + * @desc takes each element of the collection as an argument of fn + * @param {Hyphenator~elements~ElementCollection.prototype~each~callback} fn - A function that takes an element as an argument + */ each: function (fn) { var k; for (k in this.list) { @@ -683,76 +757,49 @@ var Hyphenator = (function (window) { /** - * @name Hyphenator-exceptions - * @description + * @member {Object.} Hyphenator~exceptions + * @desc * An object containing exceptions as comma separated strings for each language. * When the language-objects are loaded, their exceptions are processed, copied here and then deleted. - * @see Hyphenator-prepareLanguagesObj - * @type {Object} - * @private + * Exceptions can also be set by the user. + * @see {@link Hyphenator~prepareLanguagesObj} + * @access private */ exceptions = {}, /** - * @name Hyphenator-docLanguages - * @description + * @member {Object.} Hyphenator~docLanguages + * @desc * An object holding all languages used in the document. This is filled by - * {@link Hyphenator-gatherDocumentInfos} - * @type {Object} - * @private + * {@link Hyphenator~gatherDocumentInfos} + * @access private */ docLanguages = {}, /** - * @name Hyphenator-state - * @description - * A number that inidcates the current state of the script - * 0: not initialized - * 1: loading patterns - * 2: ready - * 3: hyphenation done - * 4: hyphenation removed - * @type {number} - * @private + * @member {string} Hyphenator~url + * @desc + * A string containing a insane RegularExpression to match URL's + * @access private */ - state = 0, - - /** - * @name Hyphenator-url - * @description - * A string containing a RegularExpression to match URL's - * @type {string} - * @private - */ - url = '(\\w*:\/\/)?((\\w*:)?(\\w*)@)?((([\\d]{1,3}\\.){3}([\\d]{1,3}))|((www\\.|[a-zA-Z]\\.)?[a-zA-Z0-9\\-\\.]+\\.([a-z]{2,4})))(:\\d*)?(\/[\\w#!:\\.?\\+=&%@!\\-]*)*', + url = '(?:\\w*:\/\/)?(?:(?:\\w*:)?(?:\\w*)@)?(?:(?:(?:[\\d]{1,3}\\.){3}(?:[\\d]{1,3}))|(?:(?:www\\.|[a-zA-Z]\\.)?[a-zA-Z0-9\\-\\.]+\\.(?:[a-z]{2,4})))(?::\\d*)?(?:\/[\\w#!:\\.?\\+=&%@!\\-]*)*', // protocoll usr pwd ip or host tld port path /** - * @name Hyphenator-mail - * @description + * @member {string} Hyphenator~mail + * @desc * A string containing a RegularExpression to match mail-adresses - * @type {string} - * @private + * @access private */ mail = '[\\w-\\.]+@[\\w\\.]+', /** - * @name Hyphenator-urlRE - * @description - * A RegularExpressions-Object for url- and mail adress matching - * @type {RegExp} - * @private - */ - urlOrMailRE = new RegExp('(' + url + ')|(' + mail + ')', 'i'), - - /** - * @name Hyphenator-zeroWidthSpace - * @description + * @member {string} Hyphenator~zeroWidthSpace + * @desc * A string that holds a char. * Depending on the browser, this is the zero with space or an empty string. * zeroWidthSpace is used to break URLs - * @type {string} - * @private + * @access private */ zeroWidthSpace = (function () { var zws, ua = window.navigator.userAgent.toLowerCase(); @@ -767,61 +814,104 @@ var Hyphenator = (function (window) { }()), /** - * @name Hyphenator-onBeforeWordHyphenation - * @description - * A method to be called for each word to be hyphenated before it is hyphenated. - * Takes the word as a first parameter and its language as a second parameter. - * Returns a string that will replace the word to be hyphenated. - * @see Hyphenator.config - * @type {function()} - * @private + * @method Hyphenator~onBeforeWordHyphenation + * @desc + * This method is called just before a word is hyphenated. + * It is called with two parameters: the word and its language. + * The method must return a string (aka the word). + * @see {@link Hyphenator.config} + * @access private + * @param {string} word + * @param {string} lang + * @return {string} The word that goes into hyphenation */ onBeforeWordHyphenation = function (word) { return word; }, /** - * @name Hyphenator-onAfterWordHyphenation - * @description - * A method to be called for each word to be hyphenated after it is hyphenated. + * @method Hyphenator~onAfterWordHyphenation + * @desc + * This method is called for each word after it is hyphenated. * Takes the word as a first parameter and its language as a second parameter. * Returns a string that will replace the word that has been hyphenated. - * @see Hyphenator.config - * @type {function()} - * @private + * @see {@link Hyphenator.config} + * @access private + * @param {string} word + * @param {string} lang + * @return {string} The word that goes into hyphenation */ onAfterWordHyphenation = function (word) { return word; }, /** - * @name Hyphenator-onHyphenationDone - * @description - * A method to be called, when the last element has been hyphenated - * @see Hyphenator.config - * @type {function()} - * @private + * @method Hyphenator~onHyphenationDone + * @desc + * A method to be called, when the last element has been hyphenated. + * If there are frames the method is called for each frame. + * Therefore the location.href of the contextWindow calling this method is given as a parameter + * @see {@link Hyphenator.config} + * @param {string} context + * @access private */ - onHyphenationDone = function () {}, + onHyphenationDone = function (context) { + return context; + }, /** - * @name Hyphenator-selectorFunction - * @description + * @name Hyphenator~selectorFunction + * @desc * A function set by the user that has to return a HTMLNodeList or array of Elements to be hyphenated. * By default this is set to false so we can check if a selectorFunction is set… - * @see Hyphenator.config - * @type {function()} - * @private + * @see {@link Hyphenator.config} + * @see {@link Hyphenator~mySelectorFunction} + * @default false + * @type {function|boolean} + * @access private */ selectorFunction = false, /** - * @name Hyphenator-mySelectorFunction - * @description - * A function that has to return a HTMLNodeList or array of Elements to be hyphenated. + * @name Hyphenator~flattenNodeList + * @desc + * Takes a nodeList and returns an array with all elements that are not contained by another element in the nodeList + * By using this function the elements returned by selectElements can be 'flattened'. + * @see {@link Hyphenator~selectElements} + * @param {nodeList} nl + * @return {Array} Array of 'parent'-elements + * @access private + */ + flattenNodeList = function (nl) { + var parentElements = [], + i = 0, + j = 0, + isParent = true; + + parentElements.push(nl[0]); //add the first item, since this is always an parent + + for (i = 1; i < nl.length; i += 1) { //cycle through nodeList + for (j = 0; j < parentElements.length; j += 1) { //cycle through parentElements + if (parentElements[j].contains(nl[i])) { + isParent = false; + break; + } + } + if (isParent) { + parentElements.push(nl[i]); + } + isParent = true; + } + + return parentElements; + }, + + /** + * @method Hyphenator~mySelectorFunction + * @desc + * A function that returns a HTMLNodeList or array of Elements to be hyphenated. * By default it uses the classname ('hyphenate') to select the elements. - * @type {function()} - * @private + * @access private */ mySelectorFunction = function (hyphenateClass) { var tmp, el = [], i, l; @@ -842,84 +932,88 @@ var Hyphenator = (function (window) { }, /** - * @name Hyphenator-selectElements - * @description - * A function that has to return a HTMLNodeList or array of Elements to be hyphenated. - * It uses either selectorFunction set by the user (and adds a unique class to each element) + * @method Hyphenator~selectElements + * @desc + * A function that uses either selectorFunction set by the user * or the default mySelectorFunction. - * @type {function()} - * @private + * @access private */ selectElements = function () { - var elements; + var elems; if (selectorFunction) { - elements = selectorFunction(); + elems = selectorFunction(); } else { - elements = mySelectorFunction(hyphenateClass); + elems = mySelectorFunction(hyphenateClass); } - - return elements; + if (elems.length !== 0) { + elems = flattenNodeList(elems); + } + return elems; }, /** - * @name Hyphenator-intermediateState - * @description - * The value of style.visibility of the text while it is hyphenated. - * @see Hyphenator.config - * @type {string} - * @private + * @member {string} Hyphenator~intermediateState + * @desc + * The visibility of elements while they are hyphenated: + * 'visible': unhyphenated text is visible and then redrawn when hyphenated. + * 'hidden': unhyphenated text is made invisible as soon as possible and made visible after hyphenation. + * @default 'hidden' + * @see {@link Hyphenator.config} + * @access private */ intermediateState = 'hidden', /** - * @name Hyphenator-unhide - * @description + * @member {string} Hyphenator~unhide + * @desc * How hidden elements unhide: either simultaneous (default: 'wait') or progressively. * 'wait' makes Hyphenator.js to wait until all elements are hyphenated (one redraw) * With 'progressive' Hyphenator.js unhides elements as soon as they are hyphenated. - * @see Hyphenator.config - * @type {string} - * @private + * @see {@link Hyphenator.config} + * @access private */ unhide = 'wait', /** - * @name Hyphenator-CSSEditors - * @description A container array that holds CSSEdit classes + * @member {Array.} Hyphenator~CSSEditors + * @desc A container array that holds CSSEdit classes * For each window object one CSSEdit class is inserted - * @see Hyphenator-CSSEdit - * @type {array} - * @private + * @access private */ CSSEditors = [], /** - * @name Hyphenator-CSSEditors - * @description A custom class with two public methods: setRule() and clearChanges() - * Rather sets style for CSS-classes then for single elements - * This is used to hide/unhide elements when they are hyphenated. - * @see Hyphenator-gatherDocumentInfos - * @type {function ()} - * @private + * @constructor Hyphenator~CSSEdit + * @desc + * This class handles access and editing of StyleSheets. + * Thanks to this styles (e.g. hiding and unhiding elements upon hyphenation) + * can be changed in one place instead for each element. + * @access private */ CSSEdit = function (w) { w = w || window; var doc = w.document, - //find/create an accessible StyleSheet + /** + * @member {Object} Hyphenator~CSSEdit~sheet + * @desc + * A StyleSheet, where Hyphenator can write to. + * If no StyleSheet can be found, lets create one. + * @access private + */ sheet = (function () { var i, l = doc.styleSheets.length, - sheet, + s, element, r = false; for (i = 0; i < l; i += 1) { - sheet = doc.styleSheets[i]; + s = doc.styleSheets[i]; try { - if (!!sheet.cssRules) { - r = sheet; + if (!!s.cssRules) { + r = s; break; } - } catch (e) {} + } catch (ignore) {} } if (r === false) { element = doc.createElement('style'); @@ -929,22 +1023,47 @@ var Hyphenator = (function (window) { } return r; }()), + + /** + * @typedef {Object} Hyphenator~CSSEdit~changes + * @property {Object} sheet - The StyleSheet where the change was made + * @property {number} index - The index of the changed rule + */ + + /** + * @member {Array.} Hyphenator~CSSEdit~changes + * @desc + * Sets a CSS rule for a specified selector + * @access private + */ changes = [], + + /** + * @typedef Hyphenator~CSSEdit~rule + * @property {number} index - The index of the rule + * @property {Object} rule - The style rule + */ + /** + * @method Hyphenator~CSSEdit~findRule + * @desc + * Searches the StyleSheets for a given selector and returns an object containing the rule. + * If nothing can be found, false is returned. + * @param {string} sel + * @return {Hyphenator~CSSEdit~rule|false} + * @access private + */ findRule = function (sel) { - var sheet, rule, sheets = window.document.styleSheets, rules, i, j, r = false; + var s, rule, sheets = w.document.styleSheets, rules, i, j, r = false; for (i = 0; i < sheets.length; i += 1) { - sheet = sheets[i]; + s = sheets[i]; try { //FF has issues here with external CSS (s.o.p) - if (!!sheet.cssRules) { - rules = sheet.cssRules; - } else if (!!sheet.rules) { + if (!!s.cssRules) { + rules = s.cssRules; + } else if (!!s.rules) { // IE < 9 - rules = sheet.rules; + rules = s.rules; } - } catch (e) { - //do nothing - //console.log(e); - } + } catch (ignore) {} if (!!rules && !!rules.length) { for (j = 0; j < rules.length; j += 1) { rule = rules[j]; @@ -959,6 +1078,15 @@ var Hyphenator = (function (window) { } return r; }, + /** + * @method Hyphenator~CSSEdit~addRule + * @desc + * Adds a rule to the {@link Hyphenator~CSSEdit~sheet} + * @param {string} sel - The selector to be added + * @param {string} rulesStr - The rules for the specified selector + * @return {number} index of the new rule + * @access private + */ addRule = function (sel, rulesStr) { var i, r; if (!!sheet.insertRule) { @@ -980,6 +1108,14 @@ var Hyphenator = (function (window) { } return r; }, + /** + * @method Hyphenator~CSSEdit~removeRule + * @desc + * Removes a rule with the specified index from the specified sheet + * @param {Object} sheet - The style sheet + * @param {number} index - the index of the rule + * @access private + */ removeRule = function (sheet, index) { if (sheet.deleteRule) { sheet.deleteRule(index); @@ -990,6 +1126,14 @@ var Hyphenator = (function (window) { }; return { + /** + * @method Hyphenator~CSSEdit.setRule + * @desc + * Sets a CSS rule for a specified selector + * @access public + * @param {string} sel - Selector + * @param {string} rulesString - CSS-Rules + */ setRule: function (sel, rulesString) { var i, existingRule, cssText; existingRule = findRule(sel); @@ -1000,26 +1144,28 @@ var Hyphenator = (function (window) { // IE < 9 cssText = existingRule.rule.style.cssText.toLowerCase(); } - if (cssText === '.' + hyphenateClass + ' { visibility: hidden; }') { - //browsers w/o IE < 9 and no additional style defs: - //add to [changes] for later removal - changes.push({sheet: existingRule.rule.parentStyleSheet, index: existingRule.index}); - } else if (cssText.indexOf('visibility: hidden') !== -1) { - // IE < 9 or additional style defs: - // add new rule + if (cssText !== sel + ' { ' + rulesString + ' }') { + //cssText of the found rule is not uniquely selector + rulesString, + if (cssText.indexOf(rulesString) !== -1) { + //maybe there are other rules or IE < 9 + //clear existing def + existingRule.rule.style.visibility = ''; + } + //add rule and register for later removal i = addRule(sel, rulesString); - //add to [changes] for later removal changes.push({sheet: sheet, index: i}); - // clear existing def - existingRule.rule.style.visibility = ''; - } else { - addRule(sel, rulesString); } } else { i = addRule(sel, rulesString); changes.push({sheet: sheet, index: i}); } }, + /** + * @method Hyphenator~CSSEdit.clearChanges + * @desc + * Removes all changes Hyphenator has made from the StyleSheets + * @access public + */ clearChanges: function () { var change = changes.pop(); while (!!change) { @@ -1031,167 +1177,217 @@ var Hyphenator = (function (window) { }, /** - * @name Hyphenator-hyphen - * @description + * @member {string} Hyphenator~hyphen + * @desc * A string containing the character for in-word-hyphenation - * @type {string} * @default the soft hyphen - * @private - * @see Hyphenator.config + * @access private + * @see {@link Hyphenator.config} */ hyphen = String.fromCharCode(173), /** - * @name Hyphenator-urlhyphen - * @description + * @member {string} Hyphenator~urlhyphen + * @desc * A string containing the character for url/mail-hyphenation - * @type {string} * @default the zero width space - * @private - * @see Hyphenator.config - * @see Hyphenator-zeroWidthSpace + * @access private + * @see {@link Hyphenator.config} + * @see {@link Hyphenator~zeroWidthSpace} */ urlhyphen = zeroWidthSpace, /** - * @name Hyphenator-safeCopy - * @description + * @method Hyphenator~hyphenateURL + * @desc + * Puts {@link Hyphenator~urlhyphen} (default: zero width space) after each no-alphanumeric char that my be in a URL. + * @param {string} url to hyphenate + * @returns string the hyphenated URL + * @access public + */ + hyphenateURL = function (url) { + var tmp = url.replace(/([:\/\.\?#&\-_,;!@]+)/gi, '$&' + urlhyphen), + parts = tmp.split(urlhyphen), + i; + for (i = 0; i < parts.length; i += 1) { + if (parts[i].length > (2 * min)) { + parts[i] = parts[i].replace(/(\w{3})(\w)/gi, "$1" + urlhyphen + "$2"); + } + } + if (parts[parts.length - 1] === "") { + parts.pop(); + } + return parts.join(urlhyphen); + }, + + /** + * @member {boolean} Hyphenator~safeCopy + * @desc * Defines wether work-around for copy issues is active or not - * Not supported by Opera (no onCopy handler) - * @type boolean * @default true - * @private - * @see Hyphenator.config - * @see Hyphenator-registerOnCopy + * @access private + * @see {@link Hyphenator.config} + * @see {@link Hyphenator~registerOnCopy} */ safeCopy = true, - /* - * runOnContentLoaded is based od jQuery.bindReady() - * see - * jQuery JavaScript Library v1.3.2 - * http://jquery.com/ - * - * Copyright (c) 2009 John Resig - * Dual licensed under the MIT and GPL licenses. - * http://docs.jquery.com/License - * - * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009) - * Revision: 6246 - */ /** - * @name Hyphenator-runOnContentLoaded - * @description - * A crossbrowser solution for the DOMContentLoaded-Event based on jQuery - * 0) { + //var efn = timeouts.shift(); + //efn(); + timeouts.shift()(); + } + } + }; + window.addEventListener("message", handleMessage, true); + return setZeroTimeOut; + }()); + } + return function (fn) { + window.setTimeout(fn, 0); + }; + }()), + + /** + * @member {Object} Hyphenator~hyphRunFor + * @desc + * stores location.href for documents where run() has been executed + * to warn when Hyphenator.run() executed multiple times + * @access private + * @see {@link Hyphenator~runWhenLoaded} + */ + hyphRunFor = {}, + + /** + * @method Hyphenator~runWhenLoaded + * @desc + * A crossbrowser solution for the DOMContentLoaded-Event based on + * jQuery * I added some functionality: e.g. support for frames and iframes… * @param {Object} w the window-object - * @param {function()} f the function to call onDOMContentLoaded - * @private + * @param {function()} f the function to call when the document is ready + * @access private */ - runOnContentLoaded = function (w, f) { - var - toplevel, hyphRunForThis = {}, + runWhenLoaded = function (w, f) { + var toplevel, add = window.document.addEventListener ? 'addEventListener' : 'attachEvent', rem = window.document.addEventListener ? 'removeEventListener' : 'detachEvent', pre = window.document.addEventListener ? '' : 'on', init = function (context) { - contextWindow = context || window; - if (!hyphRunForThis[contextWindow.location.href] && (!documentLoaded || !!contextWindow.frameElement)) { - documentLoaded = true; - f(); - hyphRunForThis[contextWindow.location.href] = true; + if (hyphRunFor[context.location.href]) { + onWarning(new Error("Warning: multiple execution of Hyphenator.run() – This may slow down the script!")); } + contextWindow = context || window; + f(); + hyphRunFor[contextWindow.location.href] = true; }, doScrollCheck = function () { try { // If IE is used, use the trick by Diego Perini // http://javascript.nwbox.com/IEContentLoaded/ - contextWindow.document.documentElement.doScroll("left"); + w.document.documentElement.doScroll("left"); } catch (error) { window.setTimeout(doScrollCheck, 1); return; } - - // and execute any waiting functions - init(window); - }, - - doOnLoad = function () { - var i, haveAccess, fl = window.frames.length; - if (doFrames && fl > 0) { - for (i = 0; i < fl; i += 1) { - haveAccess = undefined; - //try catch isn't enough for webkit - try { - //opera throws only on document.toString-access - haveAccess = window.frames[i].document.toString(); - } catch (e) { - haveAccess = undefined; - } - if (!!haveAccess) { - if (window.frames[i].location.href !== 'about:blank') { - init(window.frames[i]); - } - } - } - contextWindow = window; - f(); - hyphRunForThis[window.location.href] = true; - } else { - init(window); + //maybe modern IE fired DOMContentLoaded + if (!hyphRunFor[w.location.href]) { + documentLoaded = true; + init(w); } }, - // Cleanup functions for the document ready method - DOMContentLoaded = function (e) { - if (e.type === 'readystatechange' && contextWindow.document.readyState !== 'complete') { + doOnEvent = function (e) { + var i, fl, haveAccess; + if (!!e && e.type === 'readystatechange' && w.document.readyState !== 'interactive' && w.document.readyState !== 'complete') { return; } - contextWindow.document[rem](pre + e.type, DOMContentLoaded, false); - if (!doFrames && window.frames.length === 0) { - init(window); - } /* else { - //we are in a frameset, so do nothing but wait for onload to fire - - }*/ + + //DOM is ready/interactive, but frames may not be loaded yet! + //cleanup events + w.document[rem](pre + 'DOMContentLoaded', doOnEvent, false); + w.document[rem](pre + 'readystatechange', doOnEvent, false); + + //check frames + fl = w.frames.length; + if (fl === 0 || !doFrames) { + //there are no frames! + //cleanup events + w[rem](pre + 'load', doOnEvent, false); + documentLoaded = true; + init(w); + } else if (doFrames && fl > 0) { + //we have frames, so wait for onload and then initiate runWhenLoaded recursevly for each frame: + if (!!e && e.type === 'load') { + //cleanup events + w[rem](pre + 'load', doOnEvent, false); + for (i = 0; i < fl; i += 1) { + haveAccess = undefined; + //try catch isn't enough for webkit + try { + //opera throws only on document.toString-access + haveAccess = w.frames[i].document.toString(); + } catch (err) { + haveAccess = undefined; + } + if (!!haveAccess) { + runWhenLoaded(w.frames[i], f); + } + } + init(w); + } + } }; - - if (documentLoaded && !hyphRunForThis[w.location.href]) { - f(); - hyphRunForThis[w.location.href] = true; - return; - } - - if (contextWindow.document.readyState === "complete" || contextWindow.document.readyState === "interactive") { - //Running Hyphenator.js if it has been loaded later - //Thanks to davenewtron http://code.google.com/p/hyphenator/issues/detail?id=158#c10 - window.setTimeout(doOnLoad, 1); + if (documentLoaded || w.document.readyState === 'complete') { + //Hyphenator has run already (documentLoaded is true) or + //it has been loaded after onLoad + documentLoaded = true; + doOnEvent({type: 'load'}); } else { - //registering events - contextWindow.document[add](pre + "DOMContentLoaded", DOMContentLoaded, false); - contextWindow.document[add](pre + 'readystatechange', DOMContentLoaded, false); - window[add](pre + 'load', doOnLoad, false); + //register events + w.document[add](pre + 'DOMContentLoaded', doOnEvent, false); + w.document[add](pre + 'readystatechange', doOnEvent, false); + w[add](pre + 'load', doOnEvent, false); toplevel = false; try { toplevel = !window.frameElement; - } catch (e) {} - if (contextWindow.document.documentElement.doScroll && toplevel) { - doScrollCheck(); + } catch (ignore) {} + if (toplevel && w.document.documentElement.doScroll) { + doScrollCheck(); //calls init() } } }, /** - * @name Hyphenator-getLang - * @description - * Gets the language of an element. If no language is set, it may use the {@link Hyphenator-mainLanguage}. + * @method Hyphenator~getLang + * @desc + * Gets the language of an element. If no language is set, it may use the {@link Hyphenator~mainLanguage}. * @param {Object} el The first parameter is an DOM-Element-Object - * @param {boolean} fallback The second parameter is a boolean to tell if the function should return the {@link Hyphenator-mainLanguage} + * @param {boolean} fallback The second parameter is a boolean to tell if the function should return the {@link Hyphenator~mainLanguage} * if there's no language found for the element. - * @private + * @return {string} The language of the element + * @access private */ getLang = function (el, fallback) { try { @@ -1200,13 +1396,13 @@ var Hyphenator = (function (window) { el.tagName.toLowerCase() !== 'html' ? getLang(el.parentNode, fallback) : fallback ? mainLanguage : null; - } catch (e) {} + } catch (ignore) {} }, /** - * @name Hyphenator-autoSetMainLanguage - * @description - * Retrieves the language of the document from the DOM. + * @method Hyphenator~autoSetMainLanguage + * @desc + * Retrieves the language of the document from the DOM and sets the lang attribute of the html-tag. * The function looks in the following places: *
    *
  • lang-attribute in the html-tag
  • @@ -1214,9 +1410,9 @@ var Hyphenator = (function (window) { *
  • <meta name = "DC.Language" content = "xy" />
  • *
  • <meta name = "language" content = "xy" />
  • * - * If nothing can be found a prompt using {@link Hyphenator-languageHint} and a prompt-string is displayed. - * If the retrieved language is in the object {@link Hyphenator-supportedLangs} it is copied to {@link Hyphenator-mainLanguage} - * @private + * If nothing can be found a prompt using {@link Hyphenator~languageHint} and a prompt-string is displayed. + * If the retrieved language is in the object {@link Hyphenator~supportedLangs} it is copied to {@link Hyphenator~mainLanguage} + * @access private */ autoSetMainLanguage = function (w) { w = w || contextWindow; @@ -1224,7 +1420,7 @@ var Hyphenator = (function (window) { m = w.document.getElementsByTagName('meta'), i, getLangFromUser = function () { - var mainLanguage, + var ml, text = '', dH = 300, dW = 450, @@ -1232,8 +1428,8 @@ var Hyphenator = (function (window) { dY = Math.floor((w.outerHeight - dH) / 2) + window.screenY, ul = '', languageHint; - if (!!window.showModalDialog) { - mainLanguage = window.showModalDialog(basePath + 'modalLangDialog.html', supportedLangs, "dialogWidth: " + dW + "px; dialogHeight: " + dH + "px; dialogtop: " + dY + "; dialogleft: " + dX + "; center: on; resizable: off; scroll: off;"); + if (!!window.showModalDialog && (w.location.href.indexOf(basePath) !== -1)) { + ml = window.showModalDialog(basePath + 'modalLangDialog.html', supportedLangs, "dialogWidth: " + dW + "px; dialogHeight: " + dH + "px; dialogtop: " + dY + "; dialogleft: " + dX + "; center: on; resizable: off; scroll: off;"); } else { languageHint = (function () { var k, r = ''; @@ -1253,9 +1449,9 @@ var Hyphenator = (function (window) { text = supportedLangs.en.prompt; } text += ' (ISO 639-1)\n\n' + languageHint; - mainLanguage = window.prompt(window.unescape(text), ul).toLowerCase(); + ml = window.prompt(window.unescape(text), ul).toLowerCase(); } - return mainLanguage; + return ml; }; mainLanguage = getLang(el, false); if (!mainLanguage) { @@ -1290,70 +1486,122 @@ var Hyphenator = (function (window) { }, /** - * @name Hyphenator-gatherDocumentInfos - * @description + * @method Hyphenator~gatherDocumentInfos + * @desc * This method runs through the DOM and executes the process()-function on: - * - every node returned by the {@link Hyphenator-selectorFunction}. - * The process()-function copies the element to the elements-variable, sets its visibility - * to intermediateState, retrieves its language and recursivly descends the DOM-tree until - * the child-Nodes aren't of type 1 - * @private + * - every node returned by the {@link Hyphenator~selectorFunction}. + * @access private */ gatherDocumentInfos = function () { - var elToProcess, tmp, i = 0, - process = function (el, lang) { - var n, i = 0, hyphenate = true; + var elToProcess, urlhyphenEls, tmp, i = 0, + /** + * @method Hyphenator~gatherDocumentInfos + * @desc + * This method copies the element to the elements-variable, sets its visibility + * to intermediateState, retrieves its language and recursivly descends the DOM-tree until + * the child-Nodes aren't of type 1 + * @param {Object} el a DOM element + * @param {string} plang the language of the parent element + * @param {boolean} isChild true, if the parent of el has been processed + */ + process = function (el, pLang, isChild) { + isChild = isChild || false; + var n, j = 0, hyphenate = true, eLang, + useCSS3 = function () { + css3hyphenateClassHandle = new CSSEdit(contextWindow); + css3hyphenateClassHandle.setRule('.' + css3hyphenateClass, css3_h9n.property + ': auto;'); + css3hyphenateClassHandle.setRule('.' + dontHyphenateClass, css3_h9n.property + ': manual;'); + if ((eLang !== pLang) && css3_h9n.property.indexOf('webkit') !== -1) { + css3hyphenateClassHandle.setRule('.' + css3hyphenateClass, '-webkit-locale : ' + eLang + ';'); + } + el.className = el.className + ' ' + css3hyphenateClass; + }, + useHyphenator = function () { + //quick fix for test111.html + //better: weight elements + if (isBookmarklet && eLang !== mainLanguage) { + return; + } + if (supportedLangs.hasOwnProperty(eLang)) { + docLanguages[eLang] = true; + } else { + if (supportedLangs.hasOwnProperty(eLang.split('-')[0])) { //try subtag + eLang = eLang.split('-')[0]; + docLanguages[eLang] = true; + } else if (!isBookmarklet) { + hyphenate = false; + onError(new Error('Language "' + eLang + '" is not yet supported.')); + } + } + if (hyphenate) { + if (intermediateState === 'hidden') { + el.className = el.className + ' ' + hideClass; + } + elements.add(el, eLang); + } + }; if (el.lang && typeof (el.lang) === 'string') { - lang = el.lang.toLowerCase(); //copy attribute-lang to internal lang - } else if (lang) { - lang = lang.toLowerCase(); + eLang = el.lang.toLowerCase(); //copy attribute-lang to internal eLang + } else if (!!pLang && pLang !== '') { + eLang = pLang.toLowerCase(); } else { - lang = getLang(el, true); + eLang = getLang(el, true); } - //if css3-hyphenation is supported: use it! - if (css3 && css3_h9n.support && !!css3_h9n.checkLangSupport(lang)) { - css3hyphenateClassHandle = new CSSEdit(contextWindow); - css3hyphenateClassHandle.setRule('.' + css3hyphenateClass, css3_h9n.property + ': auto;'); - css3hyphenateClassHandle.setRule('.' + dontHyphenateClass, css3_h9n.property + ': none;'); - css3hyphenateClassHandle.setRule('.' + css3hyphenateClass, '-webkit-locale : ' + lang + ';'); - el.className = el.className + ' ' + css3hyphenateClass; - } else { - if (supportedLangs.hasOwnProperty(lang)) { - docLanguages[lang] = true; + if (!isChild) { + if (css3 && css3_h9n.support && !!css3_h9n.checkLangSupport(eLang)) { + useCSS3(); } else { - if (supportedLangs.hasOwnProperty(lang.split('-')[0])) { //try subtag - lang = lang.split('-')[0]; - docLanguages[lang] = true; - } else if (!isBookmarklet) { - hyphenate = false; - onError(new Error('Language "' + lang + '" is not yet supported.')); - } + useHyphenator(); } - if (hyphenate) { - if (intermediateState === 'hidden') { - el.className = el.className + ' ' + hideClass; + } else { + if (eLang !== pLang) { + if (css3 && css3_h9n.support && !!css3_h9n.checkLangSupport(eLang)) { + useCSS3(); + } else { + useHyphenator(); } - elements.add(el, lang); + } else { + if (!css3 || !css3_h9n.support || !css3_h9n.checkLangSupport(eLang)) { + useHyphenator(); + } // else do nothing } } - n = el.childNodes[i]; + n = el.childNodes[j]; while (!!n) { if (n.nodeType === 1 && !dontHyphenate[n.nodeName.toLowerCase()] && - n.className.indexOf(dontHyphenateClass) === -1 && !elToProcess[n]) { - process(n, lang); + n.className.indexOf(dontHyphenateClass) === -1 && + n.className.indexOf(urlHyphenateClass) === -1 && !elToProcess[n]) { + process(n, eLang, true); } - i += 1; - n = el.childNodes[i]; + j += 1; + n = el.childNodes[j]; + } + }, + processUrlStyled = function (el) { + var n, j = 0; + + n = el.childNodes[j]; + while (!!n) { + if (n.nodeType === 1 && !dontHyphenate[n.nodeName.toLowerCase()] && + n.className.indexOf(dontHyphenateClass) === -1 && + n.className.indexOf(hyphenateClass) === -1 && !urlhyphenEls[n]) { + processUrlStyled(n); + } else if (n.nodeType === 3) { + n.data = hyphenateURL(n.data); + } + j += 1; + n = el.childNodes[j]; } }; + if (css3) { css3_gethsupport(); } if (isBookmarklet) { elToProcess = contextWindow.document.getElementsByTagName('body')[0]; - process(elToProcess, mainLanguage); + process(elToProcess, mainLanguage, false); } else { if (!css3 && intermediateState === 'hidden') { CSSEditors.push(new CSSEdit(contextWindow)); @@ -1364,115 +1612,258 @@ var Hyphenator = (function (window) { elToProcess = selectElements(); tmp = elToProcess[i]; while (!!tmp) { - process(tmp, ''); + process(tmp, '', false); i += 1; tmp = elToProcess[i]; } + + urlhyphenEls = mySelectorFunction(urlHyphenateClass); + i = 0; + tmp = urlhyphenEls[i]; + while (!!tmp) { + processUrlStyled(tmp); + i += 1; + tmp = urlhyphenEls[i]; + } } if (elements.count === 0) { //nothing to hyphenate or all hyphenated by css3 for (i = 0; i < CSSEditors.length; i += 1) { CSSEditors[i].clearChanges(); } - state = 3; - onHyphenationDone(); + onHyphenationDone(contextWindow.location.href); } }, /** - * @name Hyphenator-createTrie - * @description - * converts patterns of the given language in a trie - * @private - * @param {string} lang the language whose patterns shall be converted + * @method Hyphenator~createCharMap + * @desc + * reads the charCodes from lo.characters and stores them in a bidi map: + * charMap.int2code = [0: 97, //a + * 1: 98, //b + * 2: 99] //c etc. + * charMap.code2int = {"97": 0, //a + * "98": 1, //b + * "99": 2} //c etc. + * @access private + * @param {Object} language object */ - convertPatterns = function (lang) { - /** @license BSD licenced code - * The following code is based on code from hypher.js and adapted for Hyphenator.js - * Copyright (c) 2011, Bram Stein - */ - var size = 0, - tree = { - tpoints: [] - }, - patterns, - pattern, + CharMap = function () { + this.int2code = []; + this.code2int = {}; + this.add = function (newValue) { + if (!this.code2int[newValue]) { + this.int2code.push(newValue); + this.code2int[newValue] = this.int2code.length - 1; + } + }; + }, + + /** + * @constructor Hyphenator~ValueStore + * @desc Storage-Object for storing hyphenation points (aka values) + * @access private + */ + ValueStore = function (len) { + this.keys = (function () { + var i, r; + if (Object.prototype.hasOwnProperty.call(window, "Uint8Array")) { //IE<9 doesn't have window.hasOwnProperty (host object) + return new window.Uint8Array(len); + } + r = []; + r.length = len; + for (i = r.length - 1; i >= 0; i -= 1) { + r[i] = 0; + } + return r; + }()); + this.startIndex = 1; + this.actualIndex = 2; + this.lastValueIndex = 2; + this.add = function (p) { + this.keys[this.actualIndex] = p; + this.lastValueIndex = this.actualIndex; + this.actualIndex += 1; + }; + this.add0 = function () { + //just do a step, since array is initialized with zeroes + this.actualIndex += 1; + }; + this.finalize = function () { + var start = this.startIndex; + this.keys[start] = this.lastValueIndex - start; + this.startIndex = this.lastValueIndex + 1; + this.actualIndex = this.lastValueIndex + 2; + return start; + }; + }, + + /** + * @method Hyphenator~convertPatternsToArray + * @desc + * converts the patterns to a (typed, if possible) array as described by Liang: + * + * 1. Create the CharMap: an alphabet of used character codes mapped to an int (e.g. a: "97" -> 0) + * This map is bidirectional: + * charMap.code2int is an object with charCodes as keys and corresponging ints as values + * charMao.int2code is an array of charCodes at int indizes + * the length of charMao.int2code is equal the length of the alphabet + * + * 2. Create a ValueStore: (typed) array that holds "values", i.e. the digits extracted from the patterns + * The first value starts at index 1 (since the trie is initialized with zeroes, starting at 0 would create errors) + * Each value starts with its length at index i, actual values are stored in i + n where n < length + * Trailing 0 are not stored. So pattern values like e.g. "010200" will become […,4,0,1,0,2,…] + * The ValueStore-Object manages handling of indizes automatically. Use ValueStore.add(p) to add a running value. + * Use ValueStore.finalize() when the last value of a pattern is added. It will set the length and return the starting index of the pattern. + * To prevent doubles we could temporarly store the values in a object {value: startIndex} and only add new values, + * but this object deoptimizes very fast (new hidden map for each entry); here we gain speed and pay memory + * + * 3. Create and zero initialize a (typed) array to store the trie. The trie uses two slots for each entry/node: + * i: a link to another position in the array or -1 if the pattern ends here or more rows have to be added. + * i + 1: a link to a value in the ValueStore or 0 if there's no value for the path to this node. + * Although the array is one-dimensional it can be described as an array of "rows", + * where each "row" is an array of length trieRowLength (see below). + * The first entry of this "row" represents the first character of the alphabet, the second a possible link to value store, + * the third represents the second character of the alphabet and so on… + * + * 4. Initialize trieRowLength (length of the alphabet * 2) + * + * 5. Now we apply extract to each pattern collection (patterns of the same length are collected and concatenated to one string) + * extract goes through these pattern collections char by char and adds them either to the ValueStore (if they are digits) or + * to the trie (adding more "rows" if necessary, i.e. if the last link pointed to -1). + * So the first "row" holds all starting characters, where the subsequent rows hold the characters that follow the + * character that link to this row. Therefor the array is dense at the beginning and very sparse at the end. + * + * + * @access private + * @param {Object} language object + */ + convertPatternsToArray = function (lo) { + var trieNextEmptyRow = 0, i, - j, - k, - patternObject = Hyphenator.languages[lang].patterns, - c, - chars, - points, - t, - p, - codePoint, - test = 'in3se', - rf, - getPoints = (function () { - //IE<9 doesn't act like other browsers: doesn't preserve the separators - if (test.split(/\D/).length === 1) { - rf = function (pattern) { - pattern = pattern.replace(/\D/gi, ' '); - return pattern.split(' '); - }; - } else { - rf = function (pattern) { - return pattern.split(/\D/); - }; - } - return rf; - }()); + charMapc2i, + valueStore, + indexedTrie, + trieRowLength, - for (size in patternObject) { - if (patternObject.hasOwnProperty(size)) { - patterns = patternObject[size].match(new RegExp('.{1,' + (+size) + '}', 'g')); - i = 0; - pattern = patterns[i]; - while (!!pattern) { - chars = pattern.replace(/[\d]/g, '').split(''); - points = getPoints(pattern); - t = tree; - - j = 0; - c = chars[j]; - while (!!c) { - codePoint = c.charCodeAt(0); - - if (!t[codePoint]) { - t[codePoint] = {}; + extract = function (patternSizeInt, patterns) { + var charPos = 0, + charCode = 0, + mappedCharCode = 0, + rowStart = 0, + nextRowStart = 0, + prevWasDigit = false; + for (charPos = 0; charPos < patterns.length; charPos += 1) { + charCode = patterns.charCodeAt(charPos); + if ((charPos + 1) % patternSizeInt !== 0) { + //more to come… + if (charCode <= 57 && charCode >= 49) { + //charCode is a digit + valueStore.add(charCode - 48); + prevWasDigit = true; + } else { + //charCode is alphabetical + if (!prevWasDigit) { + valueStore.add0(); + } + prevWasDigit = false; + if (nextRowStart === -1) { + nextRowStart = trieNextEmptyRow + trieRowLength; + trieNextEmptyRow = nextRowStart; + indexedTrie[rowStart + mappedCharCode * 2] = nextRowStart; + } + mappedCharCode = charMapc2i[charCode]; + rowStart = nextRowStart; + nextRowStart = indexedTrie[rowStart + mappedCharCode * 2]; + if (nextRowStart === 0) { + indexedTrie[rowStart + mappedCharCode * 2] = -1; + nextRowStart = -1; + } } - t = t[codePoint]; - j += 1; - c = chars[j]; + } else { + //last part of pattern + if (charCode <= 57 && charCode >= 49) { + //the last charCode is a digit + valueStore.add(charCode - 48); + indexedTrie[rowStart + mappedCharCode * 2 + 1] = valueStore.finalize(); + } else { + //the last charCode is alphabetical + if (!prevWasDigit) { + valueStore.add0(); + } + valueStore.add0(); + if (nextRowStart === -1) { + nextRowStart = trieNextEmptyRow + trieRowLength; + trieNextEmptyRow = nextRowStart; + indexedTrie[rowStart + mappedCharCode * 2] = nextRowStart; + } + mappedCharCode = charMapc2i[charCode]; + rowStart = nextRowStart; + if (indexedTrie[rowStart + mappedCharCode * 2] === 0) { + indexedTrie[rowStart + mappedCharCode * 2] = -1; + } + indexedTrie[rowStart + mappedCharCode * 2 + 1] = valueStore.finalize(); + } + rowStart = 0; + nextRowStart = 0; + prevWasDigit = false; } - - t.tpoints = []; - for (k = 0; k < points.length; k += 1) { - p = points[k]; - t.tpoints.push((p === "") ? 0 : p); - } - i += 1; - pattern = patterns[i]; } + };/*, + prettyPrintIndexedTrie = function (rowLength) { + var s = "0: ", + idx; + for (idx = 0; idx < indexedTrie.length; idx += 1) { + s += indexedTrie[idx]; + s += ","; + if ((idx + 1) % rowLength === 0) { + s += "\n" + (idx + 1) + ": "; + } + } + console.log(s); + };*/ + + lo.charMap = new CharMap(); + for (i = 0; i < lo.patternChars.length; i += 1) { + lo.charMap.add(lo.patternChars.charCodeAt(i)); + } + charMapc2i = lo.charMap.code2int; + + lo.valueStore = valueStore = new ValueStore(lo.valueStoreLength); + + if (Object.prototype.hasOwnProperty.call(window, "Int32Array")) { //IE<9 doesn't have window.hasOwnProperty (host object) + lo.indexedTrie = new window.Int32Array(lo.patternArrayLength * 2); + } else { + lo.indexedTrie = []; + lo.indexedTrie.length = lo.patternArrayLength * 2; + for (i = lo.indexedTrie.length - 1; i >= 0; i -= 1) { + lo.indexedTrie[i] = 0; } } - Hyphenator.languages[lang].patterns = tree; - /** - * end of BSD licenced code from hypher.js - */ + indexedTrie = lo.indexedTrie; + trieRowLength = lo.charMap.int2code.length * 2; + + for (i in lo.patterns) { + if (lo.patterns.hasOwnProperty(i)) { + extract(parseInt(i, 10), lo.patterns[i]); + } + } + //prettyPrintIndexedTrie(lo.charMap.int2code.length * 2); }, /** - * @name Hyphenator-recreatePattern - * @description + * @method Hyphenator~recreatePattern + * @desc * Recreates the pattern for the reducedPatternSet - * @private + * @param {string} pattern The pattern (chars) + * @param {string} nodePoints The nodePoints (integers) + * @access private + * @return {string} The pattern (chars and numbers) */ recreatePattern = function (pattern, nodePoints) { var r = [], c = pattern.split(''), i; - for (i = 0; i < nodePoints.length; i += 1) { - if (nodePoints[i] !== 0) { + for (i = 0; i <= c.length; i += 1) { + if (nodePoints[i] && nodePoints[i] !== 0) { r.push(nodePoints[i]); } if (c[i]) { @@ -1483,12 +1874,13 @@ var Hyphenator = (function (window) { }, /** - * @name Hyphenator-convertExceptionsToObject - * @description + * @method Hyphenator~convertExceptionsToObject + * @desc * Converts a list of comma seprated exceptions to an object: * 'Fortran,Hy-phen-a-tion' -> {'Fortran':'Fortran','Hyphenation':'Hy-phen-a-tion'} - * @private + * @access private * @param {string} exc a comma separated string of exceptions (without spaces) + * @return {Object.} */ convertExceptionsToObject = function (exc) { var w = exc.split(', '), @@ -1506,26 +1898,26 @@ var Hyphenator = (function (window) { }, /** - * @name Hyphenator-loadPatterns - * @description + * @method Hyphenator~loadPatterns + * @desc * Checks if the requested file is available in the network. * Adds a <script>-Tag to the DOM to load an externeal .js-file containing patterns and settings for the given language. - * If the given language is not in the {@link Hyphenator-supportedLangs}-Object it returns. + * If the given language is not in the {@link Hyphenator~supportedLangs}-Object it returns. * One may ask why we are not using AJAX to load the patterns. The XMLHttpRequest-Object * has a same-origin-policy. This makes the Bookmarklet impossible. * @param {string} lang The language to load the patterns for - * @private - * @see Hyphenator-basePath + * @access private + * @see {@link Hyphenator~basePath} */ - loadPatterns = function (lang) { - var url, xhr, head, script; + loadPatterns = function (lang, cb) { + var location, xhr, head, script, done = false; if (supportedLangs.hasOwnProperty(lang) && !Hyphenator.languages[lang]) { - url = basePath + 'patterns/' + supportedLangs[lang].file; + location = basePath + 'patterns/' + supportedLangs[lang].file; } else { return; } if (isLocal && !isBookmarklet) { - //check if 'url' is available: + //check if 'location' is available: xhr = null; try { // Mozilla, Opera, Safari and Internet Explorer (ab v7) @@ -1545,15 +1937,16 @@ var Hyphenator = (function (window) { } if (xhr) { - xhr.open('HEAD', url, true); + xhr.open('HEAD', location, true); xhr.setRequestHeader('Cache-Control', 'no-cache'); xhr.onreadystatechange = function () { - if (xhr.readyState === 4) { - if (xhr.status === 404) { - onError(new Error('Could not load\n' + url)); + if (xhr.readyState === 2) { + if (xhr.status >= 400) { + onError(new Error('Could not load\n' + location)); delete docLanguages[lang]; return; } + xhr.abort(); } }; xhr.send(null); @@ -1562,22 +1955,39 @@ var Hyphenator = (function (window) { if (createElem) { head = window.document.getElementsByTagName('head').item(0); script = createElem('script', window); - script.src = url; + script.src = location; script.type = 'text/javascript'; + script.charset = 'utf8'; + script.onload = script.onreadystatechange = function () { + if (!done && (!this.readyState || this.readyState === "loaded" || this.readyState === "complete")) { + done = true; + + cb(); + + // Handle memory leak in IE + script.onload = script.onreadystatechange = null; + if (head && script.parentNode) { + head.removeChild(script); + } + } + }; head.appendChild(script); } }, /** - * @name Hyphenator-prepareLanguagesObj - * @description - * Adds a cache to each language and converts the exceptions-list to an object. - * If storage is active the object is stored there. - * @private - * @param {string} lang the language ob the lang-obj + * @method Hyphenator~prepareLanguagesObj + * @desc + * Adds some feature to the language object: + * - cache + * - exceptions + * Converts the patterns to a trie using {@link Hyphenator~convertPatterns} + * @access private + * @param {string} lang The language of the language object */ prepareLanguagesObj = function (lang) { var lo = Hyphenator.languages[lang], wrd; + if (!lo.prepared) { if (enableCache) { lo.cache = {}; @@ -1607,51 +2017,62 @@ var Hyphenator = (function (window) { } else { lo.exceptions = {}; } - convertPatterns(lang); - wrd = '[\\w' + lo.specialChars + '@' + String.fromCharCode(173) + String.fromCharCode(8204) + '-]{' + min + ',}'; - lo.genRegExp = new RegExp('(' + url + ')|(' + mail + ')|(' + wrd + ')', 'gi'); + convertPatternsToArray(lo); + if (String.prototype.normalize) { + wrd = '[\\w' + lo.specialChars + lo.specialChars.normalize("NFD") + String.fromCharCode(173) + String.fromCharCode(8204) + '-]{' + min + ',}'; + } else { + wrd = '[\\w' + lo.specialChars + String.fromCharCode(173) + String.fromCharCode(8204) + '-]{' + min + ',}'; + } + lo.genRegExp = new RegExp('(' + wrd + ')|(' + url + ')|(' + mail + ')', 'gi'); lo.prepared = true; } - if (!!storage) { - try { - storage.setItem(lang, window.JSON.stringify(lo)); - } catch (e) { - onError(e); - } - } - }, - /** - * @name Hyphenator-prepare - * @description - * This funtion prepares the Hyphenator-Object: If RemoteLoading is turned off, it assumes + /**** + * @method Hyphenator~prepare + * @desc + * This funtion prepares the Hyphenator~Object: If RemoteLoading is turned off, it assumes * that the patternfiles are loaded, all conversions are made and the callback is called. * If storage is active the object is retrieved there. - * If RemoteLoading is on (default), it loads the pattern files and waits until they are loaded, - * by repeatedly checking Hyphenator.languages. If a patterfile is loaded the patterns are + * If RemoteLoading is on (default), it loads the pattern files and repeatedly checks Hyphenator.languages. + * If a patternfile is loaded the patterns are stored in storage (if enabled), * converted to their object style and the lang-object extended. * Finally the callback is called. - * @private + * @access private */ prepare = function (callback) { - var lang, interval, tmp1, tmp2; + var lang, tmp1, tmp2, + languagesLoaded = function () { + var l; + for (l in docLanguages) { + if (docLanguages.hasOwnProperty(l)) { + if (Hyphenator.languages.hasOwnProperty(l)) { + delete docLanguages[l]; + if (!!storage) { + storage.setItem(l, window.JSON.stringify(Hyphenator.languages[l])); + } + prepareLanguagesObj(l); + callback(l); + } + } + } + }; + if (!enableRemoteLoading) { for (lang in Hyphenator.languages) { if (Hyphenator.languages.hasOwnProperty(lang)) { prepareLanguagesObj(lang); } } - state = 2; callback('*'); return; } // get all languages that are used and preload the patterns - state = 1; for (lang in docLanguages) { if (docLanguages.hasOwnProperty(lang)) { if (!!storage && storage.test(lang)) { Hyphenator.languages[lang] = window.JSON.parse(storage.getItem(lang)); + prepareLanguagesObj(lang); if (exceptions.hasOwnProperty('global')) { tmp1 = convertExceptionsToObject(exceptions.global); for (tmp2 in tmp1) { @@ -1671,43 +2092,35 @@ var Hyphenator = (function (window) { delete exceptions[lang]; } //Replace genRegExp since it may have been changed: - tmp1 = '[\\w' + Hyphenator.languages[lang].specialChars + '@' + String.fromCharCode(173) + String.fromCharCode(8204) + '-]{' + min + ',}'; - Hyphenator.languages[lang].genRegExp = new RegExp('(' + url + ')|(' + mail + ')|(' + tmp1 + ')', 'gi'); - + if (String.prototype.normalize) { + tmp1 = '[\\w' + Hyphenator.languages[lang].specialChars + Hyphenator.languages[lang].specialChars.normalize("NFD") + String.fromCharCode(173) + String.fromCharCode(8204) + '-]{' + min + ',}'; + } else { + tmp1 = '[\\w' + Hyphenator.languages[lang].specialChars + String.fromCharCode(173) + String.fromCharCode(8204) + '-]{' + min + ',}'; + } + Hyphenator.languages[lang].genRegExp = new RegExp('(' + tmp1 + ')|(' + url + ')|(' + mail + ')', 'gi'); + if (enableCache) { + if (!Hyphenator.languages[lang].cache) { + Hyphenator.languages[lang].cache = {}; + } + } delete docLanguages[lang]; callback(lang); } else { - loadPatterns(lang); + loadPatterns(lang, languagesLoaded); } } } - // else async wait until patterns are loaded, then hyphenate - interval = window.setInterval(function () { - var finishedLoading = true, lang; - for (lang in docLanguages) { - if (docLanguages.hasOwnProperty(lang)) { - finishedLoading = false; - if (!!Hyphenator.languages[lang]) { - delete docLanguages[lang]; - //do conversion while other patterns are loading: - prepareLanguagesObj(lang); - callback(lang); - } - } - } - if (finishedLoading) { - window.clearInterval(interval); - state = 2; - } - }, 100); + //call languagesLoaded in case language has been loaded manually + //and remoteLoading is on (onload won't fire) + languagesLoaded(); }, /** - * @name Hyphenator-switchToggleBox - * @description - * Creates or hides the toggleBox: a small button to turn off/on hyphenation on a page. - * @see Hyphenator.config - * @private + * @method Hyphenator~toggleBox + * @desc + * Creates the toggleBox: a small button to turn off/on hyphenation on a page. + * @see {@link Hyphenator.config} + * @access private */ toggleBox = function () { var bdy, myTextNode, @@ -1726,6 +2139,7 @@ var Hyphenator = (function (window) { myBox.style.position = 'absolute'; myBox.style.top = '0px'; myBox.style.right = '0px'; + myBox.style.zIndex = '1000'; myBox.style.margin = '0'; myBox.style.backgroundColor = '#AAAAAA'; myBox.style.color = '#FFFFFF'; @@ -1740,135 +2154,204 @@ var Hyphenator = (function (window) { } }, - /** - * @name Hyphenator-hyphenateWord - * @description - * This function is the heart of Hyphenator.js. It returns a hyphenated word. + * @method Hyphenator~doCharSubst + * @desc + * Replace chars in a word * - * If there's already a {@link Hyphenator-hypen} in the word, the word is returned as it is. - * If the word is in the exceptions list or in the cache, it is retrieved from it. - * If there's a '-' put a zeroWidthSpace after the '-' and hyphenate the parts. - * @param {string} lang The language of the word - * @param {string} word The word - * @returns string The hyphenated word - * @public + * @param {Object} loCharSubst Map of substitutions ({'ä': 'a', 'ü': 'u', …}) + * @param {string} w the word + * @returns string The word with substituted characers + * @access private */ - hyphenateWord = function (lang, word) { - var lo = Hyphenator.languages[lang], parts, l, subst, - w, characters, origWord, originalCharacters, wordLength, i, j, k, node, points = [], - characterPoints = [], nodePoints, nodePointsLength, m = Math.max, trie, - result = [''], pattern, r; - word = onBeforeWordHyphenation(word, lang); - if (word === '') { - r = ''; - } else if (enableCache && lo.cache.hasOwnProperty(word)) { //the word is in the cache - r = lo.cache[word]; - } else if (word.indexOf(hyphen) !== -1) { - //word already contains shy; -> leave at it is! - r = word; - } else if (lo.exceptions.hasOwnProperty(word)) { //the word is in the exceptions list - r = lo.exceptions[word].replace(/-/g, hyphen); - } else if (word.indexOf('-') !== -1) { - //word contains '-' -> hyphenate the parts separated with '-' - parts = word.split('-'); - for (i = 0, l = parts.length; i < l; i += 1) { - parts[i] = hyphenateWord(lang, parts[i]); + doCharSubst = function (loCharSubst, w) { + var subst, r; + for (subst in loCharSubst) { + if (loCharSubst.hasOwnProperty(subst)) { + r = w.replace(new RegExp(subst, 'g'), loCharSubst[subst]); } - r = parts.join('-'); - } else { - origWord = word; - w = word = '_' + word + '_'; - if (!!lo.charSubstitution) { - for (subst in lo.charSubstitution) { - if (lo.charSubstitution.hasOwnProperty(subst)) { - w = w.replace(new RegExp(subst, 'g'), lo.charSubstitution[subst]); - } - } - } - if (origWord.indexOf("'") !== -1) { - w = w.replace("'", "’"); //replace APOSTROPHE with RIGHT SINGLE QUOTATION MARK (since the latter is used in the patterns) - } - /** @license BSD licenced code - * The following code is based on code from hypher.js - * Copyright (c) 2011, Bram Stein - */ - characters = w.toLowerCase().split(''); - originalCharacters = word.split(''); - wordLength = characters.length; - trie = lo.patterns; - for (i = 0; i < wordLength; i += 1) { - points[i] = 0; - characterPoints[i] = characters[i].charCodeAt(0); - } - for (i = 0; i < wordLength; i += 1) { - pattern = ''; - node = trie; - for (j = i; j < wordLength; j += 1) { - node = node[characterPoints[j]]; - if (node) { - if (enableReducedPatternSet) { - pattern += String.fromCharCode(characterPoints[j]); - } - nodePoints = node.tpoints; - if (nodePoints) { - if (enableReducedPatternSet) { - if (!lo.redPatSet) { - lo.redPatSet = {}; - } - lo.redPatSet[pattern] = recreatePattern(pattern, nodePoints); - } - for (k = 0, nodePointsLength = nodePoints.length; k < nodePointsLength; k += 1) { - points[i + k] = m(points[i + k], nodePoints[k]); - } - } - } else { - break; - } - } - } - for (i = 1; i < wordLength - 1; i += 1) { - if (i > lo.leftmin && i < (wordLength - lo.rightmin) && points[i] % 2) { - result.push(originalCharacters[i]); - } else { - result[result.length - 1] += originalCharacters[i]; - } - } - r = result.join(hyphen); - /** - * end of BSD licenced code from hypher.js - */ - } - r = onAfterWordHyphenation(r, lang); - if (enableCache) { //put the word in the cache - lo.cache[origWord] = r; } return r; }, /** - * @name Hyphenator-hyphenateURL - * @description - * Puts {@link Hyphenator-urlhyphen} after each no-alphanumeric char that my be in a URL. - * @param {string} url to hyphenate - * @returns string the hyphenated URL - * @public + * @member {Array} Hyphenator~wwAsMappedCharCodeStore + * @desc + * Array (typed if supported) container for charCodes + * @access private + * @see {@link Hyphenator~hyphenateWord} */ - hyphenateURL = function (url) { - return url.replace(/([:\/\.\?#&_,;!@]+)/gi, '$&' + urlhyphen); + wwAsMappedCharCodeStore = (function () { + if (Object.prototype.hasOwnProperty.call(window, "Int32Array")) { + return new window.Int32Array(32); + } + return []; + }()), + + /** + * @member {Array} Hyphenator~wwhpStore + * @desc + * Array (typed if supported) container for hyphenation points + * @access private + * @see {@link Hyphenator~hyphenateWord} + */ + wwhpStore = (function () { + var r; + if (Object.prototype.hasOwnProperty.call(window, "Uint8Array")) { + r = new window.Uint8Array(32); + } else { + r = []; + } + return r; + }()), + + /** + * @method Hyphenator~hyphenateWord + * @desc + * This function is the heart of Hyphenator.js. It returns a hyphenated word. + * + * If there's already a {@link Hyphenator~hypen} in the word, the word is returned as it is. + * If the word is in the exceptions list or in the cache, it is retrieved from it. + * If there's a '-' hyphenate the parts. + * The hyphenated word is returned and (if acivated) cached. + * Both special Events onBeforeWordHyphenation and onAfterWordHyphenation are called for the word. + * @param {Object} lo A language object (containing the patterns) + * @param {string} lang The language of the word + * @param {string} word The word + * @returns string The hyphenated word + * @access private + */ + hyphenateWord = function (lo, lang, word) { + var parts, + i, + pattern = "", + ww, + wwlen, + wwhp = wwhpStore, + pstart, + plen, + hp, + wordLength = word.length, + hw = '', + charMap = lo.charMap.code2int, + charCode, + mappedCharCode, + row = 0, + link = 0, + value = 0, + values, + indexedTrie = lo.indexedTrie, + valueStore = lo.valueStore.keys, + wwAsMappedCharCode = wwAsMappedCharCodeStore; + + word = onBeforeWordHyphenation(word, lang); + if (word === '') { + hw = ''; + } else if (enableCache && lo.cache && lo.cache.hasOwnProperty(word)) { //the word is in the cache + hw = lo.cache[word]; + } else if (word.indexOf(hyphen) !== -1) { + //word already contains shy; -> leave at it is! + hw = word; + } else if (lo.exceptions.hasOwnProperty(word)) { //the word is in the exceptions list + hw = lo.exceptions[word].replace(/-/g, hyphen); + } else if (word.indexOf('-') !== -1) { + //word contains '-' -> hyphenate the parts separated with '-' + parts = word.split('-'); + for (i = 0; i < parts.length; i += 1) { + parts[i] = hyphenateWord(lo, lang, parts[i]); + } + hw = parts.join('-'); + } else { + ww = word.toLowerCase(); + if (String.prototype.normalize) { + ww = ww.normalize("NFC"); + } + if (lo.hasOwnProperty("charSubstitution")) { + ww = doCharSubst(lo.charSubstitution, ww); + } + if (word.indexOf("'") !== -1) { + ww = ww.replace(/'/g, "’"); //replace APOSTROPHE with RIGHT SINGLE QUOTATION MARK (since the latter is used in the patterns) + } + ww = '_' + ww + '_'; + wwlen = ww.length; + //prepare wwhp and wwAsMappedCharCode + for (pstart = 0; pstart < wwlen; pstart += 1) { + wwhp[pstart] = 0; + charCode = ww.charCodeAt(pstart); + if (charMap[charCode] !== undefined) { + wwAsMappedCharCode[pstart] = charMap[charCode]; + } else { + wwAsMappedCharCode[pstart] = -1; + } + } + //get hyphenation points for all substrings + for (pstart = 0; pstart < wwlen; pstart += 1) { + row = 0; + pattern = ''; + for (plen = pstart; plen < wwlen; plen += 1) { + mappedCharCode = wwAsMappedCharCode[plen]; + if (mappedCharCode === -1) { + break; + } + if (enableReducedPatternSet) { + pattern += ww.charAt(plen); + } + link = indexedTrie[row + mappedCharCode * 2]; + value = indexedTrie[row + mappedCharCode * 2 + 1]; + if (value > 0) { + hp = valueStore[value]; + while (hp) { + hp -= 1; + if (valueStore[value + 1 + hp] > wwhp[pstart + hp]) { + wwhp[pstart + hp] = valueStore[value + 1 + hp]; + } + } + if (enableReducedPatternSet) { + if (!lo.redPatSet) { + lo.redPatSet = {}; + } + if (valueStore.subarray) { + values = valueStore.subarray(value + 1, value + 1 + valueStore[value]); + } else { + values = valueStore.slice(value + 1, value + 1 + valueStore[value]); + } + lo.redPatSet[pattern] = recreatePattern(pattern, values); + } + } + if (link > 0) { + row = link; + } else { + break; + } + } + } + //create hyphenated word + for (hp = 0; hp < wordLength; hp += 1) { + if (hp >= lo.leftmin && hp <= (wordLength - lo.rightmin) && (wwhp[hp + 1] % 2) !== 0) { + hw += hyphen + word.charAt(hp); + } else { + hw += word.charAt(hp); + } + } + } + hw = onAfterWordHyphenation(hw, lang); + if (enableCache) { //put the word in the cache + lo.cache[word] = hw; + } + return hw; }, /** - * @name Hyphenator-removeHyphenationFromElement - * @description + * @method Hyphenator~removeHyphenationFromElement + * @desc * Removes all hyphens from the element. If there are other elements, the function is * called recursively. * Removing hyphens is usefull if you like to copy text. Some browsers are buggy when the copy hyphenated texts. * @param {Object} el The element where to remove hyphenation. - * @public + * @access public */ removeHyphenationFromElement = function (el) { - var h, i = 0, n; + var h, u, i = 0, n; switch (hyphen) { case '|': h = '\\|'; @@ -1882,11 +2365,24 @@ var Hyphenator = (function (window) { default: h = hyphen; } + switch (urlhyphen) { + case '|': + u = '\\|'; + break; + case '+': + u = '\\+'; + break; + case '*': + u = '\\*'; + break; + default: + u = urlhyphen; + } n = el.childNodes[i]; while (!!n) { if (n.nodeType === 3) { n.data = n.data.replace(new RegExp(h, 'g'), ''); - n.data = n.data.replace(new RegExp(zeroWidthSpace, 'g'), ''); + n.data = n.data.replace(new RegExp(u, 'g'), ''); } else if (n.nodeType === 1) { removeHyphenationFromElement(n); } @@ -1895,135 +2391,119 @@ var Hyphenator = (function (window) { } }, - /** - * @name Hyphenator-oncopyHandler - * @description - * The function called by registerOnCopy - * @private - */ - oncopyHandler, + copy = (function () { + var Copy = function () { - /** - * @name Hyphenator-removeOnCopy - * @description - * Method to remove copy event handler from the given element - * @param object a html object from witch we remove the event - * @private - */ - removeOnCopy = function (el) { - var body = el.ownerDocument.getElementsByTagName('body')[0]; - if (!body) { - return; - } - el = el || body; - if (window.removeEventListener) { - el.removeEventListener("copy", oncopyHandler, true); - } else { - el.detachEvent("oncopy", oncopyHandler); - } - }, + this.oncopyHandler = function (e) { + e = e || window.event; + var shadow, selection, range, rangeShadow, restore, + target = e.target || e.srcElement, + currDoc = target.ownerDocument, + bdy = currDoc.getElementsByTagName('body')[0], + targetWindow = currDoc.defaultView || currDoc.parentWindow; + if (target.tagName && dontHyphenate[target.tagName.toLowerCase()]) { + //Safari needs this + return; + } + //create a hidden shadow element + shadow = currDoc.createElement('div'); + //Moving the element out of the screen doesn't work for IE9 (https://connect.microsoft.com/IE/feedback/details/663981/) + //shadow.style.overflow = 'hidden'; + //shadow.style.position = 'absolute'; + //shadow.style.top = '-5000px'; + //shadow.style.height = '1px'; + //doing this instead: + shadow.style.color = window.getComputedStyle ? targetWindow.getComputedStyle(bdy, null).backgroundColor : '#FFFFFF'; + shadow.style.fontSize = '0px'; + bdy.appendChild(shadow); + if (!!window.getSelection) { + //FF3, Webkit, IE9 + e.stopPropagation(); + selection = targetWindow.getSelection(); + range = selection.getRangeAt(0); + shadow.appendChild(range.cloneContents()); + removeHyphenationFromElement(shadow); + selection.selectAllChildren(shadow); + restore = function () { + shadow.parentNode.removeChild(shadow); + selection.removeAllRanges(); //IE9 needs that + selection.addRange(range); + }; + } else { + // IE<9 + e.cancelBubble = true; + selection = targetWindow.document.selection; + range = selection.createRange(); + shadow.innerHTML = range.htmlText; + removeHyphenationFromElement(shadow); + rangeShadow = bdy.createTextRange(); + rangeShadow.moveToElementText(shadow); + rangeShadow.select(); + restore = function () { + shadow.parentNode.removeChild(shadow); + if (range.text !== "") { + range.select(); + } + }; + } + zeroTimeOut(restore); + }; - /** - * @name Hyphenator-registerOnCopy - * @description - * Huge work-around for browser-inconsistency when it comes to - * copying of hyphenated text. - * The idea behind this code has been provided by http://github.com/aristus/sweet-justice - * sweet-justice is under BSD-License - * @param object an HTML element where the copy event will be registered to - * @private - */ - registerOnCopy = function (el) { - var body = el.ownerDocument.getElementsByTagName('body')[0], - shadow, - selection, - range, - rangeShadow, - restore; - oncopyHandler = function (e) { - e = e || window.event; - var target = e.target || e.srcElement, - currDoc = target.ownerDocument, - body = currDoc.getElementsByTagName('body')[0], - targetWindow = currDoc.defaultView || currDoc.parentWindow; - if (target.tagName && dontHyphenate[target.tagName.toLowerCase()]) { - //Safari needs this - return; - } - //create a hidden shadow element - shadow = currDoc.createElement('div'); - //Moving the element out of the screen doesn't work for IE9 (https://connect.microsoft.com/IE/feedback/details/663981/) - //shadow.style.overflow = 'hidden'; - //shadow.style.position = 'absolute'; - //shadow.style.top = '-5000px'; - //shadow.style.height = '1px'; - //doing this instead: - shadow.style.color = window.getComputedStyle ? targetWindow.getComputedStyle(body, null).backgroundColor : '#FFFFFF'; - shadow.style.fontSize = '0px'; - body.appendChild(shadow); - if (!!window.getSelection) { - //FF3, Webkit, IE9 - e.stopPropagation(); - selection = targetWindow.getSelection(); - range = selection.getRangeAt(0); - shadow.appendChild(range.cloneContents()); - removeHyphenationFromElement(shadow); - selection.selectAllChildren(shadow); - restore = function () { - shadow.parentNode.removeChild(shadow); - selection.removeAllRanges(); //IE9 needs that - selection.addRange(range); - }; - } else { - // IE<9 - e.cancelBubble = true; - selection = targetWindow.document.selection; - range = selection.createRange(); - shadow.innerHTML = range.htmlText; - removeHyphenationFromElement(shadow); - rangeShadow = body.createTextRange(); - rangeShadow.moveToElementText(shadow); - rangeShadow.select(); - restore = function () { - shadow.parentNode.removeChild(shadow); - if (range.text !== "") { - range.select(); - } - }; - } - window.setTimeout(restore, 0); + this.removeOnCopy = function (el) { + var body = el.ownerDocument.getElementsByTagName('body')[0]; + if (!body) { + return; + } + el = el || body; + if (window.removeEventListener) { + el.removeEventListener("copy", this.oncopyHandler, true); + } else { + el.detachEvent("oncopy", this.oncopyHandler); + } + }; + + this.registerOnCopy = function (el) { + var body = el.ownerDocument.getElementsByTagName('body')[0]; + if (!body) { + return; + } + el = el || body; + if (window.addEventListener) { + el.addEventListener("copy", this.oncopyHandler, true); + } else { + el.attachEvent("oncopy", this.oncopyHandler); + } + }; }; - if (!body) { - return; - } - el = el || body; - if (window.addEventListener) { - el.addEventListener("copy", oncopyHandler, true); - } else { - el.attachEvent("oncopy", oncopyHandler); - } - }, + + return (safeCopy ? new Copy() : false); + }()), + /** - * @name Hyphenator-checkIfAllDone - * @description - * Checks if all Elements are hyphenated, unhides them and fires onHyphenationDone() - * @private + * @method Hyphenator~checkIfAllDone + * @desc + * Checks if all elements in {@link Hyphenator~elements} are hyphenated, unhides them and fires onHyphenationDone() + * @access private */ checkIfAllDone = function () { - var allDone = true, i; + var allDone = true, i, doclist = {}, doc; elements.each(function (ellist) { - var i, l = ellist.length; - for (i = 0; i < l; i += 1) { - allDone = allDone && ellist[i].hyphenated; + var j, l = ellist.length; + for (j = 0; j < l; j += 1) { + allDone = allDone && ellist[j].hyphenated; + if (!doclist.hasOwnProperty(ellist[j].element.baseURI)) { + doclist[ellist[j].element.ownerDocument.location.href] = true; + } + doclist[ellist[j].element.ownerDocument.location.href] = doclist[ellist[j].element.ownerDocument.location.href] && ellist[j].hyphenated; } }); if (allDone) { if (intermediateState === 'hidden' && unhide === 'progressive') { elements.each(function (ellist) { - var i, l = ellist.length, el; - for (i = 0; i < l; i += 1) { - el = ellist[i].element; + var j, l = ellist.length, el; + for (j = 0; j < l; j += 1) { + el = ellist[j].element; el.className = el.className.replace(unhideClassRegExp, ''); if (el.className === '') { el.removeAttribute('class'); @@ -2034,76 +2514,100 @@ var Hyphenator = (function (window) { for (i = 0; i < CSSEditors.length; i += 1) { CSSEditors[i].clearChanges(); } - state = 3; - onHyphenationDone(); + for (doc in doclist) { + if (doclist.hasOwnProperty(doc) && doc === contextWindow.location.href) { + onHyphenationDone(doc); + } + } + if (!!storage && storage.deferred.length > 0) { + for (i = 0; i < storage.deferred.length; i += 1) { + storage.deferred[i].call(); + } + storage.deferred = []; + } } }, + /** + * @method Hyphenator~controlOrphans + * @desc + * removes orphans depending on the 'orphanControl'-setting: + * orphanControl === 1: do nothing + * orphanControl === 2: prevent last word to be hyphenated + * orphanControl === 3: prevent one word on a last line (inserts a nobreaking space) + * @param {string} part - The sring where orphans have to be removed + * @access private + */ + controlOrphans = function (part) { + var h, r; + switch (hyphen) { + case '|': + h = '\\|'; + break; + case '+': + h = '\\+'; + break; + case '*': + h = '\\*'; + break; + default: + h = hyphen; + } + //strip off blank space at the end (omitted closing tags) + part = part.replace(/[\s]*$/, ''); + if (orphanControl >= 2) { + //remove hyphen points from last word + r = part.split(' '); + r[1] = r[1].replace(new RegExp(h, 'g'), ''); + r[1] = r[1].replace(new RegExp(zeroWidthSpace, 'g'), ''); + r = r.join(' '); + } + if (orphanControl === 3) { + //replace spaces by non breaking spaces + r = r.replace(/[ ]+/g, String.fromCharCode(160)); + } + return r; + }, /** - * @name Hyphenator-hyphenateElement - * @description + * @method Hyphenator~hyphenateElement + * @desc * Takes the content of the given element and - if there's text - replaces the words * by hyphenated words. If there's another element, the function is called recursively. * When all words are hyphenated, the visibility of the element is set to 'visible'. - * @param {Object} el The element to hyphenate - * @private + * @param {string} lang - The language-code of the element + * @param {Element} elo - The element to hyphenate {@link Hyphenator~elements~ElementCollection~Element} + * @access private */ hyphenateElement = function (lang, elo) { var el = elo.element, hyphenate, n, i, - r, - controlOrphans = function (part) { - var h, r; - switch (hyphen) { - case '|': - h = '\\|'; - break; - case '+': - h = '\\+'; - break; - case '*': - h = '\\*'; - break; - default: - h = hyphen; - } - if (orphanControl >= 2) { - //remove hyphen points from last word - r = part.split(' '); - r[1] = r[1].replace(new RegExp(h, 'g'), ''); - r[1] = r[1].replace(new RegExp(zeroWidthSpace, 'g'), ''); - r = r.join(' '); - } - if (orphanControl === 3) { - //replace spaces by non breaking spaces - r = r.replace(/[ ]+/g, String.fromCharCode(160)); - } - return r; - }; - if (Hyphenator.languages.hasOwnProperty(lang)) { - hyphenate = function (word) { - if (!Hyphenator.doHyphenation) { - r = word; - } else if (urlOrMailRE.test(word)) { - r = hyphenateURL(word); + lo; + if (Hyphenator.languages.hasOwnProperty(lang) && Hyphenator.doHyphenation) { + lo = Hyphenator.languages[lang]; + hyphenate = function (match, word, url, mail) { + var r; + if (!!url || !!mail) { + r = hyphenateURL(match); } else { - r = hyphenateWord(lang, word); + r = hyphenateWord(lo, lang, word); } return r; }; if (safeCopy && (el.tagName.toLowerCase() !== 'body')) { - registerOnCopy(el); + copy.registerOnCopy(el); } i = 0; n = el.childNodes[i]; while (!!n) { - if (n.nodeType === 3 && n.data.length >= min) { //type 3 = #text -> hyphenate! - n.data = n.data.replace(Hyphenator.languages[lang].genRegExp, hyphenate); + if (n.nodeType === 3 //type 3 = #text + && /\S/.test(n.data) //not just white space + && n.data.length >= min) { //longer then min + n.data = n.data.replace(lo.genRegExp, hyphenate); if (orphanControl !== 1) { - n.data = n.data.replace(/[\S]+ [\S]+$/, controlOrphans); + n.data = n.data.replace(/[\S]+ [\S]+[\s]*$/, controlOrphans); } } i += 1; @@ -2126,46 +2630,49 @@ var Hyphenator = (function (window) { } }, - /** - * @name Hyphenator-hyphenateLanguageElements - * @description + * @method Hyphenator~hyphenateLanguageElements + * @desc * Calls hyphenateElement() for all elements of the specified language. * If the language is '*' then all elements are hyphenated. * This is done with a setTimout * to prevent a "long running Script"-alert when hyphenating large pages. * Therefore a tricky bind()-function was necessary. - * @private + * @param {string} lang The language of the elements to hyphenate + * @access private */ + hyphenateLanguageElements = function (lang) { - function bind(fun, arg1, arg2) { + /*function bind(fun, arg1, arg2) { return function () { return fun(arg1, arg2); }; - } + }*/ var i, l; if (lang === '*') { elements.each(function (lang, ellist) { - var i, l = ellist.length; - for (i = 0; i < l; i += 1) { - window.setTimeout(bind(hyphenateElement, lang, ellist[i]), 0); + var j, le = ellist.length; + for (j = 0; j < le; j += 1) { + //zeroTimeOut(bind(hyphenateElement, lang, ellist[j])); + hyphenateElement(lang, ellist[j]); } }); } else { if (elements.list.hasOwnProperty(lang)) { l = elements.list[lang].length; for (i = 0; i < l; i += 1) { - window.setTimeout(bind(hyphenateElement, lang, elements.list[lang][i]), 0); + //zeroTimeOut(bind(hyphenateElement, lang, elements.list[lang][i])); + hyphenateElement(lang, elements.list[lang][i]); } } } }, /** - * @name Hyphenator-removeHyphenationFromDocument - * @description - * Does what it says ;-) - * @private + * @method Hyphenator~removeHyphenationFromDocument + * @desc + * Does what it says and unregisters the onCopyEvent from the elements + * @access private */ removeHyphenationFromDocument = function () { elements.each(function (ellist) { @@ -2173,25 +2680,25 @@ var Hyphenator = (function (window) { for (i = 0; i < l; i += 1) { removeHyphenationFromElement(ellist[i].element); if (safeCopy) { - removeOnCopy(ellist[i].element); + copy.removeOnCopy(ellist[i].element); } ellist[i].hyphenated = false; } }); - state = 4; }, /** - * @name Hyphenator-createStorage - * @description - * inits the private var storage depending of the setting in storageType + * @method Hyphenator~createStorage + * @desc + * inits the private var {@link Hyphenator~storage) depending of the setting in {@link Hyphenator~storageType} * and the supported features of the system. - * @private + * @access private */ createStorage = function () { var s; try { if (storageType !== 'none' && + window.JSON !== undefined && window.localStorage !== undefined && window.sessionStorage !== undefined && window.JSON.stringify !== undefined && @@ -2207,14 +2714,19 @@ var Hyphenator = (function (window) { s = undefined; break; } + //check for private mode + s.setItem('storageTest', '1'); + s.removeItem('storageTest'); } - } catch (f) { + } catch (e) { //FF throws an error if DOM.storage.enabled is set to false + s = undefined; } if (s) { storage = { prefix: 'Hyphenator_' + Hyphenator.version + '_', store: s, + deferred: [], test: function (name) { var val = this.store.getItem(this.prefix + name); return (!!val) ? true : false; @@ -2223,7 +2735,11 @@ var Hyphenator = (function (window) { return this.store.getItem(this.prefix + name); }, setItem: function (name, value) { - this.store.setItem(this.prefix + name, value); + try { + this.store.setItem(this.prefix + name, value); + } catch (e) { + onError(e); + } } }; } else { @@ -2232,10 +2748,10 @@ var Hyphenator = (function (window) { }, /** - * @name Hyphenator-storeConfiguration - * @description + * @method Hyphenator~storeConfiguration + * @desc * Stores the current config-options in DOM-Storage - * @private + * @access private */ storeConfiguration = function () { if (!storage) { @@ -2244,6 +2760,7 @@ var Hyphenator = (function (window) { var settings = { 'STORED': true, 'classname': hyphenateClass, + 'urlclassname': urlHyphenateClass, 'donthyphenateclassname': dontHyphenateClass, 'minwordlength': min, 'hyphenchar': hyphen, @@ -2252,8 +2769,10 @@ var Hyphenator = (function (window) { 'displaytogglebox': displayToggleBox, 'remoteloading': enableRemoteLoading, 'enablecache': enableCache, + 'enablereducedpatternset': enableReducedPatternSet, 'onhyphenationdonecallback': onHyphenationDone, 'onerrorhandler': onError, + 'onwarninghandler': onWarning, 'intermediatestate': intermediateState, 'selectorfunction': selectorFunction || mySelectorFunction, 'safecopy': safeCopy, @@ -2272,10 +2791,10 @@ var Hyphenator = (function (window) { }, /** - * @name Hyphenator-restoreConfiguration - * @description + * @method Hyphenator~restoreConfiguration + * @desc * Retrieves config-options from DOM-Storage and does configuration accordingly - * @private + * @access private */ restoreConfiguration = function () { var settings; @@ -2288,77 +2807,55 @@ var Hyphenator = (function (window) { return { /** - * @name Hyphenator.version - * @memberOf Hyphenator - * @description + * @member {string} Hyphenator.version + * @desc * String containing the actual version of Hyphenator.js * [major release].[minor releas].[bugfix release] * major release: new API, new Features, big changes * minor release: new languages, improvements - * @public + * @access public */ - version: '4.2.0', + version: '5.1.0', /** - * @name Hyphenator.doHyphenation - * @description - * If doHyphenation is set to false (defaults to true), hyphenateDocument() isn't called. + * @member {boolean} Hyphenator.doHyphenation + * @desc + * If doHyphenation is set to false, hyphenateDocument() isn't called. * All other actions are performed. + * @default true */ doHyphenation: true, /** - * @name Hyphenator.languages - * @memberOf Hyphenator - * @description + * @typedef {Object} Hyphenator.languages.language + * @property {Number} leftmin - The minimum of chars to remain on the old line + * @property {Number} rightmin - The minimum of chars to go on the new line + * @property {string} specialChars - Non-ASCII chars in the alphabet. + * @property {Object.} patterns - the patterns in a compressed format. The key is the length of the patterns in the value string. + * @property {Object.} charSubstitution - optional: a hash table with chars that are replaced during hyphenation + * @property {string | Object.} exceptions - optional: a csv string containing exceptions + */ + + /** + * @member {Object.} Hyphenator.languages + * @desc * Objects that holds key-value pairs, where key is the language and the value is the * language-object loaded from (and set by) the pattern file. - * The language object holds the following members: - * - * - * - * - * - * - * - * - *
    keydesc>
    leftminThe minimum of chars to remain on the old line
    rightminThe minimum of chars to go on the new line
    shortestPatternThe shortes pattern (numbers don't count!)
    longestPatternThe longest pattern (numbers don't count!)
    specialCharsNon-ASCII chars in the alphabet.
    patternsthe patterns
    - * And optionally (or after prepareLanguagesObj() has been called): - * - * - *
    exceptionsExcpetions for the secified language
    - * @public + * @namespace Hyphenator.languages + * @access public */ languages: {}, /** - * @name Hyphenator.config - * @description - * Config function that takes an object as an argument. The object contains key-value-pairs - * containig Hyphenator-settings. This is a shortcut for calling Hyphenator.set...-Methods. - * @param {Object} obj - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - *
    keyvaluesdefault
    classnamestring'hyphenate'
    donthyphenateclassnamestring''
    minwordlengthinteger6
    hyphencharstring'&shy;'
    urlhyphencharstring'zero with space'
    toggleboxfunctionsee code
    displaytoggleboxbooleanfalse
    remoteloadingbooleantrue
    enablecachebooleantrue
    enablereducedpatternsetbooleanfalse
    onhyphenationdonecallbackfunctionempty function
    onerrorhandlerfunctionalert(onError)
    intermediatestatestring'hidden'
    selectorfunctionfunction[…]
    safecopybooleantrue
    doframesbooleanfalse
    storagetypestring'none'
    - * @public - * @example <script src = "Hyphenator.js" type = "text/javascript"></script> + * @method Hyphenator.config + * @desc + * The Hyphenator.config() function that takes an object as an argument. The object contains key-value-pairs + * containig Hyphenator-settings. + * @param {Hyphenator.config} obj + * @access public + * @example + * <script src = "Hyphenator.js" type = "text/javascript"></script>  * <script type = "text/javascript">  * Hyphenator.config({'minwordlength':4,'hyphenchar':'|'}); * Hyphenator.run(); @@ -2400,6 +2897,11 @@ var Hyphenator = (function (window) { hyphenateClass = obj[key]; } break; + case 'urlclassname': + if (assert('urlclassname', 'string')) { + urlHyphenateClass = obj[key]; + } + break; case 'donthyphenateclassname': if (assert('donthyphenateclassname', 'string')) { dontHyphenateClass = obj[key]; @@ -2460,6 +2962,11 @@ var Hyphenator = (function (window) { onError = obj[key]; } break; + case 'onwarninghandler': + if (assert('onwarninghandler', 'function')) { + onWarning = obj[key]; + } + break; case 'intermediatestate': if (assert('intermediatestate', 'string')) { intermediateState = obj[key]; @@ -2536,16 +3043,21 @@ var Hyphenator = (function (window) { }, /** - * @name Hyphenator.run - * @description - * Bootstrap function that starts all hyphenation processes when called. - * @public - * @example <script src = "Hyphenator.js" type = "text/javascript"></script> + * @method Hyphenator.run + * @desc + * Bootstrap function that starts all hyphenation processes when called: + * Tries to create storage if required and calls {@link Hyphenator~runWhenLoaded} on 'window' handing over the callback 'process' + * @access public + * @example + * <script src = "Hyphenator.js" type = "text/javascript"></script>  * <script type = "text/javascript">  *   Hyphenator.run();  * </script> */ run: function () { + /** + *@callback Hyphenator.run~process process - The function is called when the DOM has loaded (or called for each frame) + */ var process = function () { try { if (contextWindow.document.getElementsByTagName('frameset').length > 0) { @@ -2553,51 +3065,29 @@ var Hyphenator = (function (window) { } autoSetMainLanguage(undefined); gatherDocumentInfos(); - prepare(hyphenateLanguageElements); if (displayToggleBox) { toggleBox(); } + prepare(hyphenateLanguageElements); } catch (e) { onError(e); } - }, i, haveAccess, fl = window.frames.length; + }; if (!storage) { createStorage(); } - if (!documentLoaded && !isBookmarklet) { - runOnContentLoaded(window, process); - } - if (isBookmarklet || documentLoaded) { - if (doFrames && fl > 0) { - for (i = 0; i < fl; i += 1) { - haveAccess = undefined; - //try catch isn't enough for webkit - try { - //opera throws only on document.toString-access - haveAccess = window.frames[i].document.toString(); - } catch (e) { - haveAccess = undefined; - } - if (!!haveAccess) { - contextWindow = window.frames[i]; - process(); - } - } - } - contextWindow = window; - process(); - } + runWhenLoaded(window, process); }, /** - * @name Hyphenator.addExceptions - * @description + * @method Hyphenator.addExceptions + * @desc * Adds the exceptions from the string to the appropriate language in the - * {@link Hyphenator-languages}-object + * {@link Hyphenator~languages}-object * @param {string} lang The language * @param {string} words A comma separated string of hyphenated words WITH spaces. - * @public + * @access public * @example <script src = "Hyphenator.js" type = "text/javascript"></script>  * <script type = "text/javascript">  *   Hyphenator.addExceptions('de','ziem-lich, Wach-stube'); @@ -2616,15 +3106,15 @@ var Hyphenator = (function (window) { }, /** - * @name Hyphenator.hyphenate - * @public - * @description + * @method Hyphenator.hyphenate + * @access public + * @desc * Hyphenates the target. The language patterns must be loaded. * If the target is a string, the hyphenated string is returned, - * if it's an object, the values are hyphenated directly. + * if it's an object, the values are hyphenated directly and undefined (aka nothing) is returned * @param {string|Object} target the target to be hyphenated * @param {string} lang the language of the target - * @returns string + * @returns {string|undefined} * @example <script src = "Hyphenator.js" type = "text/javascript"></script> * <script src = "patterns/en.js" type = "text/javascript"></script>  * <script type = "text/javascript"> @@ -2632,17 +3122,18 @@ var Hyphenator = (function (window) { * </script> */ hyphenate: function (target, lang) { - var hyphenate, n, i; + var hyphenate, n, i, lo; + lo = Hyphenator.languages[lang]; if (Hyphenator.languages.hasOwnProperty(lang)) { - if (!Hyphenator.languages[lang].prepared) { + if (!lo.prepared) { prepareLanguagesObj(lang); } - hyphenate = function (word) { + hyphenate = function (match, word, url, mail) { var r; - if (urlOrMailRE.test(word)) { - r = hyphenateURL(word); + if (!!url || !!mail) { + r = hyphenateURL(match); } else { - r = hyphenateWord(lang, word); + r = hyphenateWord(lo, lang, word); } return r; }; @@ -2650,8 +3141,10 @@ var Hyphenator = (function (window) { i = 0; n = target.childNodes[i]; while (!!n) { - if (n.nodeType === 3 && n.data.length >= min) { //type 3 = #text -> hyphenate! - n.data = n.data.replace(Hyphenator.languages[lang].genRegExp, hyphenate); + if (n.nodeType === 3 //type 3 = #text + && /\S/.test(n.data) //not just white space + && n.data.length >= min) { //longer then min + n.data = n.data.replace(lo.genRegExp, hyphenate); } else if (n.nodeType === 1) { if (n.lang !== '') { Hyphenator.hyphenate(n, n.lang); @@ -2663,7 +3156,7 @@ var Hyphenator = (function (window) { n = target.childNodes[i]; } } else if (typeof target === 'string' || target.constructor === String) { - return target.replace(Hyphenator.languages[lang].genRegExp, hyphenate); + return target.replace(lo.genRegExp, hyphenate); } } else { onError(new Error('Language "' + lang + '" is not loaded.')); @@ -2671,28 +3164,34 @@ var Hyphenator = (function (window) { }, /** - * @name Hyphenator.getRedPatternSet - * @description - * Returns {@link Hyphenator-isBookmarklet}. + * @method Hyphenator.getRedPatternSet + * @desc + * Returns the reduced pattern set: an object looking like: {'patk': pat} * @param {string} lang the language patterns are stored for - * @returns object {'patk': pat} - * @public + * @returns {Object.} + * @access public */ getRedPatternSet: function (lang) { return Hyphenator.languages[lang].redPatSet; }, /** - * @name Hyphenator.isBookmarklet - * @description - * Returns {@link Hyphenator-isBookmarklet}. - * @returns boolean - * @public + * @method Hyphenator.isBookmarklet + * @desc + * Returns {@link Hyphenator~isBookmarklet}. + * @returns {boolean} + * @access public */ isBookmarklet: function () { return isBookmarklet; }, + /** + * @method Hyphenator.getConfigFromURI + * @desc + * reads and sets configurations from GET parameters in the URI + * @access public + */ getConfigFromURI: function () { /*jslint evil: true*/ var loc = null, re = {}, jsArray = contextWindow.document.getElementsByTagName('script'), i, j, l, s, gp, option; @@ -2713,7 +3212,12 @@ var Hyphenator = (function (window) { } else if (isFinite(option[1])) { option[1] = parseInt(option[1], 10); } - if (option[0] === 'onhyphenationdonecallback') { + if (option[0] === 'togglebox' || + option[0] === 'onhyphenationdonecallback' || + option[0] === 'onerrorhandler' || + option[0] === 'selectorfunction' || + option[0] === 'onbeforewordhyphenation' || + option[0] === 'onafterwordhyphenation') { option[1] = new Function('', option[1]); } re[option[0]] = option[1]; @@ -2726,10 +3230,10 @@ var Hyphenator = (function (window) { }, /** - * @name Hyphenator.toggleHyphenation - * @description + * @method Hyphenator.toggleHyphenation + * @desc * Checks the current state of the ToggleBox and removes or does hyphenation. - * @public + * @access public */ toggleHyphenation: function () { if (Hyphenator.doHyphenation) { @@ -2744,16 +3248,17 @@ var Hyphenator = (function (window) { if (!!css3hyphenateClassHandle) { css3hyphenateClassHandle.setRule('.' + css3hyphenateClass, css3_h9n.property + ': auto;'); } - hyphenateLanguageElements('*'); Hyphenator.doHyphenation = true; + hyphenateLanguageElements('*'); storeConfiguration(); toggleBox(); } } }; }(window)); + //Export properties/methods (for google closure compiler) -/* to be moved to external file +/**** to be moved to external file Hyphenator['languages'] = Hyphenator.languages; Hyphenator['config'] = Hyphenator.config; Hyphenator['run'] = Hyphenator.run; @@ -2765,8 +3270,12 @@ Hyphenator['getConfigFromURI'] = Hyphenator.getConfigFromURI; Hyphenator['toggleHyphenation'] = Hyphenator.toggleHyphenation; window['Hyphenator'] = Hyphenator; */ + +/* + * call Hyphenator if it is a Bookmarklet + */ if (Hyphenator.isBookmarklet()) { - Hyphenator.config({displaytogglebox: true, intermediatestate: 'visible', doframes: true, useCSS3hyphenation: true}); + Hyphenator.config({displaytogglebox: true, intermediatestate: 'visible', storagetype: 'local', doframes: true, useCSS3hyphenation: true}); Hyphenator.config(Hyphenator.getConfigFromURI()); Hyphenator.run(); } \ No newline at end of file diff --git a/resources/viewer/hyphenate/patterns.zip b/resources/viewer/hyphenate/patterns.zip index 56fda41783..a29121cd82 100644 Binary files a/resources/viewer/hyphenate/patterns.zip and b/resources/viewer/hyphenate/patterns.zip differ