UNPKG

typograf

Version:

The client and server typography

1,824 lines (1,744 loc) 98.3 kB
/*! typograf | © 2026 Denis Seleznev | MIT License | https://github.com/typograf/typograf */ /****************************************************************************** Copyright (c) Microsoft Corporation. Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. ***************************************************************************** */ /* global Reflect, Promise, SuppressedError, Symbol, Iterator */ var __assign = function() { __assign = Object.assign || function __assign(t) { for (var s, i = 1, n = arguments.length; i < n; i++) { s = arguments[i]; for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; } return t; }; return __assign.apply(this, arguments); }; function __spreadArray(to, from, pack) { if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) { if (ar || !(i in from)) { if (!ar) ar = Array.prototype.slice.call(from, 0, i); ar[i] = from[i]; } } return to.concat(ar || Array.prototype.slice.call(from)); } typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) { var e = new Error(message); return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e; }; // http://www.w3.org/TR/html4/sgml/entities var visibleEntities = [ ['iexcl', 161], ['cent', 162], ['pound', 163], ['curren', 164], ['yen', 165], ['brvbar', 166], ['sect', 167], ['uml', 168], ['copy', 169], ['ordf', 170], ['laquo', 171], ['not', 172], ['reg', 174], ['macr', 175], ['deg', 176], ['plusmn', 177], ['sup2', 178], ['sup3', 179], ['acute', 180], ['micro', 181], ['para', 182], ['middot', 183], ['cedil', 184], ['sup1', 185], ['ordm', 186], ['raquo', 187], ['frac14', 188], ['frac12', 189], ['frac34', 190], ['iquest', 191], ['Agrave', 192], ['Aacute', 193], ['Acirc', 194], ['Atilde', 195], ['Auml', 196], ['Aring', 197], ['AElig', 198], ['Ccedil', 199], ['Egrave', 200], ['Eacute', 201], ['Ecirc', 202], ['Euml', 203], ['Igrave', 204], ['Iacute', 205], ['Icirc', 206], ['Iuml', 207], ['ETH', 208], ['Ntilde', 209], ['Ograve', 210], ['Oacute', 211], ['Ocirc', 212], ['Otilde', 213], ['Ouml', 214], ['times', 215], ['Oslash', 216], ['Ugrave', 217], ['Uacute', 218], ['Ucirc', 219], ['Uuml', 220], ['Yacute', 221], ['THORN', 222], ['szlig', 223], ['agrave', 224], ['aacute', 225], ['acirc', 226], ['atilde', 227], ['auml', 228], ['aring', 229], ['aelig', 230], ['ccedil', 231], ['egrave', 232], ['eacute', 233], ['ecirc', 234], ['euml', 235], ['igrave', 236], ['iacute', 237], ['icirc', 238], ['iuml', 239], ['eth', 240], ['ntilde', 241], ['ograve', 242], ['oacute', 243], ['ocirc', 244], ['otilde', 245], ['ouml', 246], ['divide', 247], ['oslash', 248], ['ugrave', 249], ['uacute', 250], ['ucirc', 251], ['uuml', 252], ['yacute', 253], ['thorn', 254], ['yuml', 255], ['fnof', 402], ['Alpha', 913], ['Beta', 914], ['Gamma', 915], ['Delta', 916], ['Epsilon', 917], ['Zeta', 918], ['Eta', 919], ['Theta', 920], ['Iota', 921], ['Kappa', 922], ['Lambda', 923], ['Mu', 924], ['Nu', 925], ['Xi', 926], ['Omicron', 927], ['Pi', 928], ['Rho', 929], ['Sigma', 931], ['Tau', 932], ['Upsilon', 933], ['Phi', 934], ['Chi', 935], ['Psi', 936], ['Omega', 937], ['alpha', 945], ['beta', 946], ['gamma', 947], ['delta', 948], ['epsilon', 949], ['zeta', 950], ['eta', 951], ['theta', 952], ['iota', 953], ['kappa', 954], ['lambda', 955], ['mu', 956], ['nu', 957], ['xi', 958], ['omicron', 959], ['pi', 960], ['rho', 961], ['sigmaf', 962], ['sigma', 963], ['tau', 964], ['upsilon', 965], ['phi', 966], ['chi', 967], ['psi', 968], ['omega', 969], ['thetasym', 977], ['upsih', 978], ['piv', 982], ['bull', 8226], ['hellip', 8230], ['prime', 8242], ['Prime', 8243], ['oline', 8254], ['frasl', 8260], ['weierp', 8472], ['image', 8465], ['real', 8476], ['trade', 8482], ['alefsym', 8501], ['larr', 8592], ['uarr', 8593], ['rarr', 8594], ['darr', 8595], ['harr', 8596], ['crarr', 8629], ['lArr', 8656], ['uArr', 8657], ['rArr', 8658], ['dArr', 8659], ['hArr', 8660], ['forall', 8704], ['part', 8706], ['exist', 8707], ['empty', 8709], ['nabla', 8711], ['isin', 8712], ['notin', 8713], ['ni', 8715], ['prod', 8719], ['sum', 8721], ['minus', 8722], ['lowast', 8727], ['radic', 8730], ['prop', 8733], ['infin', 8734], ['ang', 8736], ['and', 8743], ['or', 8744], ['cap', 8745], ['cup', 8746], ['int', 8747], ['there4', 8756], ['sim', 8764], ['cong', 8773], ['asymp', 8776], ['ne', 8800], ['equiv', 8801], ['le', 8804], ['ge', 8805], ['sub', 8834], ['sup', 8835], ['nsub', 8836], ['sube', 8838], ['supe', 8839], ['oplus', 8853], ['otimes', 8855], ['perp', 8869], ['sdot', 8901], ['lceil', 8968], ['rceil', 8969], ['lfloor', 8970], ['rfloor', 8971], ['lang', 9001], ['rang', 9002], ['spades', 9824], ['clubs', 9827], ['hearts', 9829], ['diams', 9830], ['loz', 9674], ['OElig', 338], ['oelig', 339], ['Scaron', 352], ['scaron', 353], ['Yuml', 376], ['circ', 710], ['tilde', 732], ['ndash', 8211], ['mdash', 8212], ['lsquo', 8216], ['rsquo', 8217], ['sbquo', 8218], ['ldquo', 8220], ['rdquo', 8221], ['bdquo', 8222], ['dagger', 8224], ['Dagger', 8225], ['permil', 8240], ['lsaquo', 8249], ['rsaquo', 8250], ['euro', 8364], ['NestedGreaterGreater', 8811], ['NestedLessLess', 8810] ]; var invisibleEntities = [ ['nbsp', 160], ['thinsp', 8201], ['ensp', 8194], ['emsp', 8195], ['shy', 173], ['zwnj', 8204], ['zwj', 8205], ['lrm', 8206], ['rlm', 8207] ]; var HtmlEntities = /** @class */ (function () { function HtmlEntities() { var _this = this; this.entities = this.prepareEntities(__spreadArray(__spreadArray([], visibleEntities, true), invisibleEntities, true)); this.entitiesByName = {}; this.entitiesByNameEntity = {}; this.entitiesByDigitEntity = {}; this.entitiesByJsEntity = {}; this.entitiesByUtf = {}; this.entities.forEach(function (entity) { _this.entitiesByName[entity.name] = entity; _this.entitiesByNameEntity[entity.type.name] = entity; _this.entitiesByDigitEntity[entity.type.digit] = entity; _this.entitiesByJsEntity[entity.type.js] = entity; _this.entitiesByUtf[entity.utf] = entity; }); this.invisibleEntities = this.prepareEntities(invisibleEntities); } /** * Entities as name or digit to UTF-8. */ HtmlEntities.prototype.toUtf = function (context) { var _this = this; // &#160; if (context.text.search(/&#/) !== -1) { context.text = this.decHexToUtf(context.text); } // &nbsp; if (context.text.search(/&[a-z]/i) !== -1) { // 2 - min length of entity without & and ;. Example: &DD; // 31 - max length of entity without & and ;. Example: &CounterClockwiseContourIntegral; context.text = context.text.replace(/&[a-z\d]{2,31};/gi, function (key) { var entity = _this.entitiesByNameEntity[key]; return entity ? entity.utf : key; }); } // \u00a0 if (context.text.search(/\\u[\da-f]/i) !== -1) { context.text = context.text.replace(/\\u[\da-f]{4};/gi, function (key) { var entity = _this.entitiesByJsEntity[key.toLowerCase()]; return entity ? entity.utf : key; }); } }; /** * Entities in decimal or hexadecimal form to UTF-8. */ HtmlEntities.prototype.decHexToUtf = function (text) { return text .replace(/&#(\d{1,6});/gi, function ($0, $1) { return String.fromCharCode(parseInt($1, 10)); }) .replace(/&#x([\da-f]{1,6});/gi, function ($0, $1) { return String.fromCharCode(parseInt($1, 16)); }); }; /** * Restore HTML entities in text. */ HtmlEntities.prototype.restore = function (context) { var params = context.prefs.htmlEntity; var type = params.type; if (type === 'default') { return; } var entities = this.entities; if (params.onlyInvisible || params.list) { entities = []; if (params.onlyInvisible) { entities = entities.concat(this.invisibleEntities); } if (params.list) { entities = entities.concat(this.prepareListParam(params.list)); } } context.text = this.restoreEntitiesByIndex(context.text, type, entities); }; /** * Get a entity by utf using the type. */ HtmlEntities.prototype.getByUtf = function (symbol, type) { switch (type) { case 'digit': return this.entitiesByDigitEntity[symbol]; case 'name': return this.entitiesByNameEntity[symbol]; case 'js': return this.entitiesByJsEntity[symbol]; } return symbol; }; HtmlEntities.prototype.prepareEntities = function (entities) { var result = []; entities.forEach(function (entity) { var name = entity[0], digit = entity[1]; var utf = String.fromCharCode(digit); result.push({ name: name, utf: utf, // \u00a0 reUtf: new RegExp(utf, 'g'), type: { name: '&' + name + ';', // &nbsp; digit: '&#' + digit + ';', // &#160; js: '\\u' + ('0000' + digit.toString(16)).slice(-4), // \u00a0 }, }); }); return result; }; HtmlEntities.prototype.prepareListParam = function (list) { var _this = this; var result = []; list.forEach(function (name) { var entity = _this.entitiesByName[name]; if (entity) { result.push(entity); } }); return result; }; HtmlEntities.prototype.restoreEntitiesByIndex = function (text, type, entities) { entities.forEach(function (entity) { text = text.replace(entity.reUtf, entity.type[type]); }); return text; }; return HtmlEntities; }()); var htmlEntities = new HtmlEntities(); var locales = []; function addLocale(locale) { var code = (locale || '').split('/')[0]; if (code && code !== 'common' && !hasLocale(code)) { locales.push(code); locales.sort(); } } function getLocales() { return locales; } function hasLocale(locale) { return locale === 'common' || locales.indexOf(locale) !== -1; } function prepareLocale(locale1, locale2) { var locale = locale1 || locale2; if (!locale) { return []; } return Array.isArray(locale) ? locale : [locale]; } function checkLocales(locales) { if (!locales.length) { throw Error('Not defined the property "locale".'); } locales.forEach(function (locale) { if (!hasLocale(locale)) { throw Error("\"".concat(locale, "\" is not supported locale.")); } }); } var data$1 = {}; /** * Get data for use in rules. */ function getData(key) { return data$1[key]; } /** * Set data for use in rules. */ function setData(newData) { Object.keys(newData).forEach(function (key) { addLocale(key); data$1[key] = newData[key]; }); } var inlineElements = [ 'a', 'abbr', 'acronym', 'b', 'bdo', 'big', 'br', 'button', 'cite', 'code', 'dfn', 'em', 'i', 'img', 'input', 'kbd', 'label', 'map', 'object', 'q', 'samp', 'script', 'select', 'small', 'span', 'strong', 'sub', 'sup', 'textarea', 'time', 'tt', 'var' ]; var regExpUrl = new RegExp('(https?|file|ftp)://([a-zA-Z0-9/+-=%&:_.~?]+[a-zA-Z0-9#+]*)', 'g'); var regExpNumber = '\\d+([.,]\\d+)?'; var regExpDigit = /\d/; function isDigit(symbol) { return symbol.search(regExpDigit) > -1; } var privateLabel = '\uF000'; var privateSeparateLabel = '\uF001'; var SafeTags = /** @class */ (function () { function SafeTags() { this.groups = ['own', 'html', 'url']; this.hidden = {}; this.counter = 0; var html = [ ['<!--', '-->'], ['<!ENTITY', '>'], ['<!DOCTYPE', '>'], ['<\\?xml', '\\?>'], ['<!\\[CDATA\\[', '\\]\\]>'] ]; [ 'code', 'kbd', 'object', 'pre', 'samp', 'script', 'style', 'var' ].forEach(function (tag) { html.push([ "<".concat(tag, "(\\s[^>]*?)?>"), "</".concat(tag, ">") ]); }); this.tags = { own: [], html: html.map(this.prepareRegExp), url: [regExpUrl] }; } /** * Add own safe tag. */ SafeTags.prototype.add = function (tag) { this.tags.own.push(this.prepareRegExp(tag)); }; /** * Show safe tags. */ SafeTags.prototype.show = function (context, group) { var reReplace = new RegExp(privateLabel + 'tf\\d+' + privateLabel, 'g'); var reSearch = new RegExp(privateLabel + 'tf\\d'); var replaceLabel = function (match) { return context.safeTags.hidden[group][match] || match; }; for (var i = 0, len = this.tags[group].length; i < len; i++) { context.text = context.text.replace(reReplace, replaceLabel); if (context.text.search(reSearch) === -1) { break; } } }; /** * Hide safe tags. */ SafeTags.prototype.hide = function (context, group) { var _this = this; context.safeTags.hidden[group] = {}; var pasteLabel = this.pasteLabel.bind(this, context, group); this.tags[group].forEach(function (tag) { context.text = context.text.replace(_this.prepareRegExp(tag), pasteLabel); }); }; /** * Hide HTML tags. */ SafeTags.prototype.hideHTMLTags = function (context) { if (context.isHTML) { var pasteLabel = this.pasteLabel.bind(this, context, 'html'); context.text = context.text .replace(/<\/?[a-z][^]*?>/gi, pasteLabel) // Tags .replace(/&lt;\/?[a-z][^]*?&gt;/gi, pasteLabel) // Escaping tags .replace(/&[gl]t;/gi, pasteLabel); } }; /** * Get previous label. */ SafeTags.prototype.getPrevLabel = function (text, position) { for (var i = position - 1; i >= 0; i--) { if (text[i] === privateLabel) { return text.slice(i, position + 1); } } return ''; }; SafeTags.prototype.getNextLabel = function (text, position) { for (var i = position + 1; i < text.length; i++) { if (text[i] === privateLabel) { return text.slice(position, i + 1); } } return ''; }; SafeTags.prototype.getTagByLabel = function (context, label) { var result = null; this.groups.some(function (group) { var value = context.safeTags.hidden[group][label]; if (typeof value !== 'undefined') { result = { group: group, value: value }; } return result; }); return result; }; SafeTags.prototype.getTagInfo = function (tag) { if (!tag) { return null; } var result = { group: tag.group }; switch (tag.group) { case 'html': result.name = tag.value.split(/[<\s>]/)[1]; result.isInline = inlineElements.indexOf(result.name) > -1; result.isClosing = tag.value.search(/^<\//) > -1; break; case 'url': result.isInline = true; break; case 'own': result.isInline = false; break; } return result; }; SafeTags.prototype.pasteLabel = function (context, group, match) { var safeTags = context.safeTags; var key = privateLabel + 'tf' + safeTags.counter + privateLabel; safeTags.hidden[group][key] = match; safeTags.counter++; return key; }; SafeTags.prototype.prepareRegExp = function (tag) { if (tag instanceof RegExp) { return tag; } var startTag = tag[0], endTag = tag[1], middle = tag[2]; return new RegExp(startTag + (typeof middle === 'undefined' ? '[^]*?' : middle) + endTag, 'gi'); }; SafeTags.prototype.getPrevTagInfo = function (context, text, pos) { var prevLabel = this.getPrevLabel(text, pos - 1); if (prevLabel) { var prevTag = this.getTagByLabel(context, prevLabel); if (prevTag) { return this.getTagInfo(prevTag); } } return null; }; SafeTags.prototype.getNextTagInfo = function (context, text, pos) { var nextLabel = this.getNextLabel(text, pos + 1); if (nextLabel) { var nextTag = this.getTagByLabel(context, nextLabel); if (nextTag) { return this.getTagInfo(nextTag); } } return null; }; return SafeTags; }()); function repeat(symbol, count) { var result = ''; for (;;) { if ((count & 1) === 1) { result += symbol; } count >>>= 1; if (count === 0) { break; } symbol += symbol; } return result; } function replaceNbsp$1(text) { return text.replace(/\u00A0/g, ' '); } function replace(text, re) { for (var i = 0; i < re.length; i++) { text = text.replace(re[i][0], re[i][1]); } return text; } function isHTML(text) { return text.search(/(<\/?[a-z]|<!|&[lg]t;)/i) !== -1; } function removeCR(text) { return text.replace(/\r\n?/g, '\n'); } function fixLineEnding(text, type) { if (type === 'CRLF') { // Windows return text.replace(/\n/g, '\r\n'); } else if (type === 'CR') { // Mac return text.replace(/\n/g, '\r'); } return text; } /** * Get a deep copy of a object. */ function deepCopy(obj) { return typeof obj === 'object' ? JSON.parse(JSON.stringify(obj)) : obj; } var groupIndexes = { symbols: 110, 'number': 150, space: 210, dash: 310, punctuation: 410, nbsp: 510, money: 710, date: 810, other: 910, optalign: 1010, typo: 1110, html: 1210 }; var DEFAULT_RULE_INDEX = 0; var DEFAULT_QUEUE_NAME = 'default'; var rules = []; var innerRules = []; function addInnerRule(rule) { innerRules.push(prepareRule(rule)); } function addRule(rule) { var preparedRule = prepareRule(rule); addLocale(preparedRule.locale); rules.push(preparedRule); } function sortRules(rules) { rules.sort(function (a, b) { return a.index > b.index ? 1 : -1; }); } function getRules() { var result = __spreadArray([], rules, true); sortRules(result); return result; } function getInnerRules() { return __spreadArray([], innerRules, true); } function getRuleIndex(rule) { if (typeof rule.index === 'number') { return rule.index; } var _a = rule.name.split('/'), group = _a[1]; var groupIndex = groupIndexes[group]; if (typeof groupIndex === 'undefined') { groupIndex = DEFAULT_RULE_INDEX; } if (typeof rule.index === 'string') { return groupIndex + parseInt(rule.index, 10); } return groupIndex; } function prepareRule(rule) { var _a = rule.name.split('/'), locale = _a[0], group = _a[1], shortName = _a[2]; var preparedRule = { name: rule.name, shortName: shortName, handler: rule.handler, queue: rule.queue || DEFAULT_QUEUE_NAME, enabled: rule.disabled === true ? false : true, locale: locale, group: group, index: getRuleIndex(rule), settings: rule.settings, live: rule.live, htmlAttrs: rule.htmlAttrs, }; return preparedRule; } var PACKAGE_VERSION = '7.7.0'; function prepareHtmlEntity(htmlEntity) { var result = { type: (htmlEntity === null || htmlEntity === void 0 ? void 0 : htmlEntity.type) || 'default', list: htmlEntity === null || htmlEntity === void 0 ? void 0 : htmlEntity.list, onlyInvisible: Boolean(htmlEntity === null || htmlEntity === void 0 ? void 0 : htmlEntity.onlyInvisible), }; return result; } function prepareLineEnding(lineEnding) { return lineEnding || 'LF'; } function preparePrefs(prefs) { var result = { locale: prepareLocale(prefs.locale), lineEnding: prepareLineEnding(prefs.lineEnding), live: Boolean(prefs.live), ruleFilter: prefs.ruleFilter, enableRule: prefs.enableRule, disableRule: prefs.disableRule, processingSeparateParts: prefs.processingSeparateParts, htmlEntity: prepareHtmlEntity(prefs.htmlEntity), }; return result; } function prepareContextPrefs(prefs, executePrefs) { var result = __assign({}, prefs); if (!executePrefs) { return result; } if ('locale' in executePrefs) { result.locale = prepareLocale(executePrefs.locale); } if ('htmlEntity' in executePrefs) { result.htmlEntity = prepareHtmlEntity(executePrefs.htmlEntity); } if ('lineEnding' in executePrefs) { result.lineEnding = prepareLineEnding(executePrefs.lineEnding); } if ('processingSeparateParts' in executePrefs) { result.processingSeparateParts = executePrefs.processingSeparateParts; } if ('ruleFilter' in executePrefs) { result.ruleFilter = executePrefs.ruleFilter; } return result; } var Typograf = /** @class */ (function () { function Typograf(prefs) { var _this = this; this.rules = []; this.innerRules = []; this.rulesByQueues = {}; this.innerRulesByQueues = {}; this.separatePartsTags = [ 'title', 'p', 'h[1-6]', 'select', 'legend', ]; this.prefs = preparePrefs(prefs); checkLocales(this.prefs.locale); this.safeTags = new SafeTags(); this.settings = {}; this.enabledRules = {}; this.innerRulesByQueues = {}; this.innerRules = getInnerRules(); this.innerRules.forEach(function (rule) { _this.innerRulesByQueues[rule.queue] = _this.innerRulesByQueues[rule.queue] || []; _this.innerRulesByQueues[rule.queue].push(rule); }); this.rulesByQueues = {}; this.rules = getRules(); this.rules.forEach(function (rule) { _this.prepareRuleSettings(rule); _this.rulesByQueues[rule.queue] = _this.rulesByQueues[rule.queue] || []; _this.rulesByQueues[rule.queue].push(rule); }); if (this.prefs.disableRule) { this.disableRule(this.prefs.disableRule); } if (this.prefs.enableRule) { this.enableRule(this.prefs.enableRule); } } Typograf.addRule = function (rule) { addRule(rule); }; Typograf.addRules = function (rules) { var _this = this; rules.forEach(function (item) { _this.addRule(item); }); }; /** * Add internal rule. * Internal rules are executed before main rules. */ Typograf.addInnerRule = function (rule) { addInnerRule(rule); }; Typograf.addInnerRules = function (rules) { var _this = this; rules.forEach(function (item) { _this.addInnerRule(item); }); }; Typograf.getRule = function (ruleName) { var rule = null; var rules = getRules(); rules.some(function (item) { if (item.name === ruleName) { rule = item; return true; } return false; }); return rule; }; Typograf.getRules = function () { return getRules(); }; Typograf.getInnerRules = function () { return getInnerRules(); }; Typograf.getLocales = function () { return getLocales(); }; Typograf.addLocale = function (locale) { addLocale(locale); }; Typograf.hasLocale = function (locale) { return hasLocale(locale); }; Typograf.setData = function (data) { setData(data); }; Typograf.getData = function (key) { return getData(key); }; /** * Execute typographical rules for text. */ Typograf.prototype.execute = function (text, prefs) { text = '' + text; if (!text) { return ''; } var contextPrefs = prepareContextPrefs(this.prefs, prefs); checkLocales(contextPrefs.locale); var context = this.prepareContext(text, contextPrefs); return this.process(context); }; Typograf.prototype.getSetting = function (ruleName, setting) { return this.settings[ruleName] && this.settings[ruleName][setting]; }; Typograf.prototype.setSetting = function (ruleName, setting, value) { this.settings[ruleName] = this.settings[ruleName] || {}; this.settings[ruleName][setting] = value; }; Typograf.prototype.isEnabledRule = function (ruleName) { return this.enabledRules[ruleName] !== false; }; Typograf.prototype.isDisabledRule = function (ruleName) { return !this.enabledRules[ruleName]; }; Typograf.prototype.enableRule = function (ruleName) { return this.enable(ruleName, true); }; Typograf.prototype.disableRule = function (ruleName) { return this.enable(ruleName, false); }; /** * Add safe tag. * * @example * // const typograf = new Typograf({ locale: 'ru' }); * // typograf.addSafeTag('<mytag>', '</mytag>'); * // typograf.addSafeTag('<mytag>', '</mytag>', '.*?'); * // typograf.addSafeTag(/<mytag>.*?</mytag>/gi); */ Typograf.prototype.addSafeTag = function (startTag, endTag, middle) { var tag = startTag instanceof RegExp ? startTag : [startTag, endTag, middle]; this.safeTags.add(tag); }; Typograf.prototype.prepareContext = function (text, prefs) { var context = { text: text, isHTML: isHTML(text), prefs: prefs, getData: function (key) { if (key === 'char') { return prefs.locale.map(function (item) { return getData(item + '/' + key); }).join(''); } else { return getData(prefs.locale[0] + '/' + key); } }, safeTags: this.safeTags, }; return context; }; Typograf.prototype.splitBySeparateParts = function (context) { if (!context.isHTML || context.prefs.processingSeparateParts === false) { return [context.text]; } var text = []; var reTags = new RegExp('<(' + this.separatePartsTags.join('|') + ')(\\s[^>]*?)?>[^]*?</\\1>', 'gi'); var position = 0; context.text.replace(reTags, function ($0, $1, $2, itemPosition) { if (position !== itemPosition) { text.push((position ? privateSeparateLabel : '') + context.text.slice(position, itemPosition) + privateSeparateLabel); } text.push($0); position = itemPosition + $0.length; return $0; }); text.push(position ? (privateSeparateLabel + context.text.slice(position, context.text.length)) : context.text); return text; }; Typograf.prototype.process = function (context) { var _this = this; context.text = removeCR(context.text); this.executeRules(context, 'start'); this.safeTags.hide(context, 'own'); this.executeRules(context, 'hide-safe-tags-own'); this.safeTags.hide(context, 'html'); this.executeRules(context, 'hide-safe-tags-html'); var isRootHTML = context.isHTML; var re = new RegExp(privateSeparateLabel, 'g'); context.text = this.splitBySeparateParts(context).map(function (item) { context.text = item; context.isHTML = isHTML(item); _this.safeTags.hideHTMLTags(context); _this.safeTags.hide(context, 'url'); _this.executeRules(context, 'hide-safe-tags-url'); _this.executeRules(context, 'hide-safe-tags'); htmlEntities.toUtf(context); if (context.prefs.live) { context.text = replaceNbsp$1(context.text); } _this.executeRules(context, 'utf'); _this.executeRules(context); htmlEntities.restore(context); _this.executeRules(context, 'html-entities'); _this.safeTags.show(context, 'url'); _this.executeRules(context, 'show-safe-tags-url'); return context.text.replace(re, ''); }).join(''); context.isHTML = isRootHTML; this.safeTags.show(context, 'html'); this.executeRules(context, 'show-safe-tags-html'); this.safeTags.show(context, 'own'); this.executeRules(context, 'show-safe-tags-own'); this.executeRules(context, 'end'); return fixLineEnding(context.text, context.prefs.lineEnding); }; Typograf.prototype.executeRules = function (context, queue) { var _this = this; if (queue === void 0) { queue = DEFAULT_QUEUE_NAME; } var rules = this.rulesByQueues[queue]; var innerRules = this.innerRulesByQueues[queue]; if (innerRules) { innerRules.forEach(function (rule) { _this.ruleIterator(context, rule); }); } if (rules) { rules.forEach(function (rule) { _this.ruleIterator(context, rule); }); } }; Typograf.prototype.ruleIterator = function (context, rule) { if ((context.prefs.live === true && rule.live === false) || (context.prefs.live === false && rule.live === true)) { return; } if ((rule.locale === 'common' || rule.locale === context.prefs.locale[0]) && this.isEnabledRule(rule.name)) { if (context.prefs.ruleFilter && !context.prefs.ruleFilter(rule)) { return; } if (this.onBeforeRule) { this.onBeforeRule(rule.name, context); } context.text = rule.handler.call(this, context.text, this.settings[rule.name], context); if (this.onAfterRule) { this.onAfterRule(rule.name, context); } } }; Typograf.prototype.prepareRuleSettings = function (rule) { this.settings[rule.name] = deepCopy(rule.settings); this.enabledRules[rule.name] = rule.enabled; }; Typograf.prototype.enable = function (ruleName, enabled) { var _this = this; if (Array.isArray(ruleName)) { ruleName.forEach(function (item) { _this.enableByMask(item, enabled); }); } else { this.enableByMask(ruleName, enabled); } }; Typograf.prototype.enableByMask = function (ruleName, enabled) { var _this = this; if (!ruleName) { return; } if (ruleName.search(/\*/) !== -1) { var re_1 = new RegExp(ruleName .replace(/\//g, '\\/') .replace(/\*/g, '.*')); this.rules.forEach(function (el) { var name = el.name; if (re_1.test(name)) { _this.enabledRules[name] = enabled; } }); } else { this.enabledRules[ruleName] = enabled; } }; Typograf.groups = []; Typograf.titles = {}; Typograf.version = PACKAGE_VERSION; return Typograf; }()); var common = { 'common/char': 'a-z', 'common/dash': '--?|‒|–|—', // --, &#8210, &ndash, &mdash 'common/quote': '«‹»›„“‟”"', }; var be = { 'be/char': 'абвгдежзйклмнопрстуфхцчшыьэюяёіўґ', 'be/quote': { left: '«“', right: '»”', } }; var bg = { 'bg/char': 'абвгдежзийклмнопрстуфхцчшщъьюя', 'bg/quote': { left: '„’', right: '“’', } }; var ca = { 'ca/char': 'abcdefghijlmnopqrstuvxyzàçèéíïòóúü', 'ca/quote': { left: '«“', right: '»”', } }; var cs = { 'cs/char': 'a-záéíóúýčďěňřšťůž', 'cs/quote': { left: '„‚', right: '“‘', } }; var da = { 'da/char': 'a-zåæø', 'da/quote': { left: '»›', right: '«‹', } }; var de = { 'de/char': 'a-zßäöü', 'de/quote': { left: '„‚', right: '“‘', } }; var el = { 'el/char': 'ΐάέήίΰαβγδεζηθικλμνξοπρςστυφχψωϊϋόύώϲάέήίόύώ', 'el/quote': { left: '«“', right: '»”', } }; var enGB = { 'en-GB/char': 'a-z', 'en-GB/quote': { left: '‘“', right: '’”', }, 'en-GB/shortWord': 'a|an|and|as|at|bar|but|by|for|if|in|nor|not|of|off|on|or|out|per|pro|so|the|to|up|via|yet', }; var enUS = { 'en-US/char': 'a-z', 'en-US/quote': { left: '“‘', right: '”’', }, 'en-US/shortWord': 'a|an|and|as|at|bar|but|by|for|if|in|nor|not|of|off|on|or|out|per|pro|so|the|to|up|via|yet', }; var eo = { 'eo/char': 'abcdefghijklmnoprstuvzĉĝĥĵŝŭ', 'eo/quote': { left: '“‘', right: '”’', } }; var es = { 'es/char': 'a-záéíñóúü', 'es/quote': { left: '«“', right: '»”', } }; var et = { 'et/char': 'abdefghijklmnoprstuvzäõöüšž', 'et/quote': { left: '„«', right: '“»', } }; var fi = { 'fi/char': 'abcdefghijklmnopqrstuvyöäå', 'fi/quote': { left: '”’', right: '”’', } }; var fr = { 'fr/char': 'a-zàâçèéêëîïôûüœæ', 'fr/quote': { left: '«‹', right: '»›', spacing: true, } }; var ga = { 'ga/char': 'abcdefghilmnoprstuvwxyzáéíóú', 'ga/quote': { left: '“‘', right: '”’', } }; var hu = { 'hu/char': 'a-záäéíóöúüőű', 'hu/quote': { left: '„»', right: '”«', } }; var it = { 'it/char': 'a-zàéèìòù', 'it/quote': { left: '«“', right: '»”', }, 'it/shortWord': 'a|da|di|in|la|il|lo|e|o|se|su|che|come|ma|è|ho|ha|sa', }; var lv = { 'lv/char': 'abcdefghijklmnopqrstuvxzæœ', 'lv/quote': { left: '«„', right: '»“', } }; var nl = { 'nl/char': 'a-zäçèéêëîïñöûü', 'nl/quote': { left: '‘“', right: '’”', } }; var no = { 'no/char': 'a-zåæèéêòóôø', 'no/quote': { left: '«’', right: '»’', } }; var pl = { 'pl/char': 'abcdefghijklmnoprstuvwxyzóąćęłńśźż', 'pl/quote': { left: '„«', right: '”»', } }; var ro = { 'ro/char': 'abcdefghijklmnoprstuvxzîășț', 'ro/quote': { left: '„«', right: '”»', } }; var ru = { 'ru/char': 'а-яё', 'ru/dashBefore': '(^| |\\n)', 'ru/dashAfter': '(?=[\u00A0 ,.?:!]|$)', 'ru/dashAfterDe': '(?=[,.?:!]|[\u00A0 ][^А-ЯЁ]|$)', 'ru/l': 'а-яёa-z', 'ru/L': 'А-ЯЁA-Z', 'ru/month': 'январь|февраль|март|апрель|май|июнь|июль|август|сентябрь|октябрь|ноябрь|декабрь', 'ru/monthGenCase': 'января|февраля|марта|апреля|мая|июня|июля|августа|сентября|октября|ноября|декабря', 'ru/monthPreCase': 'январе|феврале|марте|апреле|мае|июне|июле|августе|сентябре|октябре|ноябре|декабре', 'ru/quote': { left: '«„‚', right: '»“‘', removeDuplicateQuotes: true, }, 'ru/shortMonth': 'янв|фев|мар|апр|ма[ейя]|июн|июл|авг|сен|окт|ноя|дек', 'ru/shortWord': 'а|без|в|во|если|да|до|для|за|и|или|из|к|ко|как|ли|на|но|не|ни|о|об|обо|от|по|про|при|под|с|со|то|у', 'ru/weekday': 'понедельник|вторник|среда|четверг|пятница|суббота|воскресенье', }; var sk = { 'sk/char': 'abcdefghijklmnoprstuvwxyzáäéíóôúýčďľňŕšťž', 'sk/quote': { left: '„‚', right: '“‘', } }; var sl = { 'sl/char': 'a-zčšž', 'sl/quote': { left: '„‚', right: '“‘', } }; var sr = { 'sr/char': 'abcdefghijklmnoprstuvzćčđšž', 'sr/quote': { left: '„’', right: '”’', } }; var sv = { 'sv/char': 'a-zäåéö', 'sv/quote': { left: '”’', right: '”’', } }; var tr = { 'tr/char': 'abcdefghijklmnoprstuvyzâçîöûüğış', 'tr/quote': { left: '“‘', right: '”’', } }; var uk = { 'uk/char': 'абвгдежзийклмнопрстуфхцчшщьюяєіїґ', 'uk/quote': { left: '«„', right: '»“', } }; var data = [ common, be, bg, ca, cs, da, de, el, enGB, enUS, eo, es, et, fi, fr, ga, hu, it, lv, nl, no, pl, ro, ru, sk, sl, sr, sv, tr, uk ]; data.forEach(function (item) { return setData(item); }); var eMailRule = { name: 'common/html/e-mail', queue: 'end', handler: function (text, _settings, context) { return context.isHTML ? text : text.replace(/(^|[\s;(])([\w\-.]{2,64})@([\w\-.]{2,64})\.([a-z]{2,64})([)\s.,!?]|$)/gi, '$1<a href="mailto:$2@$3.$4">$2@$3.$4</a>$5'); }, disabled: true, htmlAttrs: false, }; var entityMap = { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', '\'': '&#39;', '/': '&#x2F;' }; var escapeRule = { name: 'common/html/escape', index: '+100', queue: 'end', handler: function (text) { return text.replace(/[&<>"'/]/g, function (key) { return entityMap[key]; }); }, disabled: true, }; var nbrRule = { name: 'common/html/nbr', index: '+10', queue: 'end', handler: function (text) { return text.replace(/([^\n>])\n(?=[^\n])/g, '$1<br/>\n'); }, disabled: true, htmlAttrs: false, }; var blockElements = [ 'address', 'article', 'aside', 'blockquote', 'canvas', 'dd', 'div', 'dl', 'fieldset', 'figcaption', 'figure', 'footer', 'form', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'header', 'hgroup', 'hr', 'li', 'main', 'nav', 'noscript', 'ol', 'output', 'p', 'pre', 'section', 'table', 'tfoot', 'ul', 'video' ]; var blockRe = new RegExp('<(' + blockElements.join('|') + ')[>\\s]'); var separator = '\n\n'; var pRule = { name: 'common/html/p', index: '+5', queue: 'end', handler: function (text) { var buffer = text.split(separator); buffer.forEach(function (text, i, data) { if (!text.trim()) { return; } if (!blockRe.test(text)) { data[i] = text.replace(/^(\s*)/, '$1<p>').replace(/(\s*)$/, '</p>$1'); } }); return buffer.join(separator); }, disabled: true, htmlAttrs: false, }; var processingAttrsRule = { name: 'common/html/processingAttrs', queue: 'hide-safe-tags-own', // After "hide-safe-tags-own", before "hide-safe-tags-html". handler: function (text, settings, context) { var _this = this; var reAttrs = new RegExp('(^|\\s)(' + settings.attrs.join('|') + ')=("[^"]*?"|\'[^\']*?\')', 'gi'); var prefs = deepCopy(context.prefs); prefs.ruleFilter = function (rule) { return rule.htmlAttrs !== false; }; return text.replace(/(<[-\w]+\s)([^>]+?)(?=>)/g, function (_match, tagName, attrs) { var resultAttrs = attrs.replace(reAttrs, function (_submatch, space, attrName, attrValue) { var lquote = attrValue[0]; var rquote = attrValue[attrValue.length - 1]; var value = attrValue.slice(1, -1); return space + attrName + '=' + lquote + _this.execute(value, prefs) + rquote; }); return tagName + resultAttrs; }); }, settings: { attrs: ['title', 'placeholder'] }, disabled: true, htmlAttrs: false, }; var quotRule = { name: 'common/html/quot', queue: 'hide-safe-tags', handler: function (text) { return text.replace(/&quot;/g, '"'); }, }; var stripTagsRule = { name: 'common/html/stripTags', index: '+99', queue: 'end', handler: function (text) { return text.replace(/<[^>]+>/g, ''); }, disabled: true, }; var urlRule = { name: 'common/html/url', queue: 'end', handler: function (text, _settings, context) { return context.isHTML ? text : text.replace(regExpUrl, function ($0, protocol, path) { path = path .replace(/([^/]+\/?)(\?|#)$/, '$1') // Remove ending ? and # .replace(/^([^/]+)\/$/, '$1'); // Remove ending / if (protocol === 'http') { path = path.replace(/^([^/]+)(:80)([^\d]|\/|$)/, '$1$3'); // Remove 80 port } else if (protocol === 'https') { path = path.replace(/^([^/]+)(:443)([^\d]|\/|$)/, '$1$3'); // Remove 443 port } var url = path; var fullUrl = protocol + '://' + path; var firstPart = '<a href="' + fullUrl + '">'; if (protocol === 'http' || protocol === 'https') { url = url.replace(/^www\./, ''); return firstPart + (protocol === 'http' ? url : protocol + '://' + url) + '</a>'; } return firstPart + fullUrl + '</a>'; }); }, disabled: true, htmlAttrs: false, }; Typograf.addRules([ eMailRule, escapeRule, nbrRule, pRule, processingAttrsRule, quotRule, stripTagsRule, urlRule, ]); var afterNumberRule = { name: 'common/nbsp/afterNumber', handler: function (text, _settings, context) { var char = context.getData('char'); var re = '(^|\\s)(\\d{1,5}) ([' + char + ']+)'; return text.replace(new RegExp(re, 'gi'), '$1$2\u00A0$3'); }, disabled: true, }; var afterParagraphMarkRule = { name: 'common/nbsp/afterParagraphMark', handler: function (text) { return text.replace(/¶ ?(?=\d)/g, '¶\u00A0'); }, }; var afterSectionMarkRule = { name: 'common/nbsp/afterSectionMark', handler: function (text, _settings, context) { // \u2009 - THIN SPACE // \u202F - NARROW NO-BREAK SPACE var locale = context.prefs.locale[0]; return text.replace(/§[ \u00A0\u2009]?(?=\d|I|V|X)/g, locale === 'ru' ? '§\u202F' : '§\u00A0'); }, }; var afterShortWordRule = { name: 'common/nbsp/afterShortWord', handler: function (text, settings, context) { var lengthShortWord = settings.lengthShortWord; var quote = getData('common/quote'); var char = context.getData('char'); var before = ' \u00A0(' + privateLabel + quote; var subStr = '(^|[' + before + '])([' + char + ']{1,' + lengthShortWord + '}) '; var newSubStr = '$1$2\u00A0'; var re = new RegExp(subStr, 'gim'); return text .replace(re, newSubStr) .replace(re, newSubStr); }, settings: { lengthShortWord: 2, }, }; var afterShortWordByListRule = { name: 'common/nbsp/afterShortWordByList', handler: function (text, _, context) { var quote = getData('common/quote'); var shortWord = context.getData('shortWord'); var before = ' \u00A0(' + privateLabel + quote; var subStr = '(^|[' + before + '])(' + shortWord + ') '; var newSubStr = '$1$2\u00A0'; var re = new RegExp(subStr, 'gim'); return text .replace(re, newSubStr) .replace(re, newSubStr); }, }; var beforeShortLastNumberRule = { name: 'common/nbsp/beforeShortLastNumber', handler: function (text, settings, context) { var quote = context.getData('quote'); var ch = context.getData('char'); var CH = ch.toUpperCase(); var re = new RegExp('([' + ch + CH + ']) (?=\\d{1,' + settings.lengthLastNumber + '}[-+−%\'"' + quote.right + ')]?([.!?…]( [' + CH + ']|$)|$))', 'gm'); return text.replace(re, '$1\u00A0'); }, live: false, settings: { lengthLastNumber: 2, }, }; var beforeShortLastWordRule = { name: 'common/nbsp/beforeShortLastWord', handler: function (text, settings, context) { var ch = context.getData('char'); var CH = ch.toUpperCase(); var re = new RegExp('([' + ch + '\\d]) ([' + ch + CH + ']{1,' + settings.lengthLastWord + '}[.!?…])( [' + CH + ']|$)', 'g'); return text.replace(re, '$1\u00A0$2$3'); }, settings: { lengthLastWord: 3, }, }; var dpiRule = { name: 'common/nbsp/dpi', handler: function (text) { return text.replace(/(\d) ?(lpi|dpi)(?!\w)/, '$1\u00A0$2'); }, }; function replaceNbsp($0, $1, $2, $3) { return $1 + $2.replace(/([^\u00A0])\u00A0([^\u00A0])/g, '$1 $2') + $3; } var nowrapRule = { name: 'common/nbsp/nowrap', queue: 'end', handler: function (text) { return text .replace(/(<nowrap>)(.*?)(<\/nowrap>)/g, replaceNbsp) .replace(/(<nobr>)(.*?)(<\/nobr>)/g, replaceNbsp); }, }; var replaceNbspRule = { name: 'common/nbsp/replaceNbsp', queue: 'utf', live: false, handler: replaceNbsp$1, disabled: true, }; Typograf.addRules([ afterNumberRule, afterParagraphMarkRule, afterSectionMarkRule, afterShortWordRule, afterShortWordByListRule, beforeShortLastNumberRule, beforeShortLastWordRule, dpiRule, nowrapRule, replaceNbspRule, ]); var digitGroupingRule = { name: 'common/number/digitGrouping', index: '310', disabled: true, handler: function (text, settings) { return text .replace(new RegExp("(^ ?|\\D |".concat(privateLabel, ")(\\d{1,3}([ \u00A0\u202F\u2009]\\d{3})+)(?! ?[\\d-])"), 'gm'), function ($0, $1, $2) { return $1 + $2.replace(/\s/g, settings.space); }) // https://www.bipm.org/utils/common/pdf/si-brochure/SI-Brochure-9-EN.pdf #5.4.4 .replace(/(\d{5,}([.,]\d+)?)/g, function ($0, $1) { var decimalMarker = $1.match(/[.,]/); var parts = decimalMarker ? $1.split(decimalMarker) : [$1]; var integerPart = parts[0]; var fractionalPart = parts[1]; integerPart = integerPart.replace(/(\d)(?=(\d{3})+([^\d]|$))/g, '$1' + settings.space); return decimalMarker ? integerPart + decimalMarker + fractionalPart : integerPart; }); }, settings: { space: '\u202F', }, }; var fractionRule = { name: 'common/number/fraction', handler: function (text) { return text .replace(/(^|\D)1\/2(\D|$)/g, '$1½$2') .replace(/(^|\D)1\/4(\D|$)/g, '$1¼$2') .replace(/(^|\D)3\/4(\D|$)/g, '$1¾$2'); }, }; var mathSignsRule = { name: 'common/number/mathSigns', handler: function (text) { return replace(text, [ [/!=/g, '≠'], [/<=/g, '≤'], [/(^|[^=])>=/g, '$1≥'], [/<=>/g, '⇔'], [/<</g, '≪'], [/>>/g, '≫'], [/~=/g, '≅'], [/(^|[^+])\+-/g, '$1±'] ]); } }; var timesRule = { name: 'common/number/times', handler: function (text) { return text.replace(/(\d)[ \u00A0]?[xх][ \u00A0]?(\d)/g, '$1×$2'); }, }; Typograf.addRules([ digitGroupingRule, fractionRule, mathSignsRule, timesRule, ]); var delBOMRule = { name: 'common/other/delBOM', queue: 'start', index: -1, handler: function (te