3006 lines
80 KiB
JavaScript
3006 lines
80 KiB
JavaScript
|
/*!
|
|||
|
* 漢字標準格式 v3.3.0 | MIT License | css.hanzi.co
|
|||
|
* Han.css: the CSS typography framework optimised for Hanzi
|
|||
|
*/
|
|||
|
|
|||
|
void function( global, factory ) {
|
|||
|
|
|||
|
// CommonJS
|
|||
|
if ( typeof module === 'object' && typeof module.exports === 'object' ) {
|
|||
|
module.exports = factory( global, true )
|
|||
|
// AMD
|
|||
|
} else if ( typeof define === 'function' && define.amd ) {
|
|||
|
define(function() { return factory( global, true ) })
|
|||
|
// Global namespace
|
|||
|
} else {
|
|||
|
factory( global )
|
|||
|
}
|
|||
|
|
|||
|
}( typeof window !== 'undefined' ? window : this, function( window, noGlobalNS ) {
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var document = window.document
|
|||
|
|
|||
|
var root = document.documentElement
|
|||
|
|
|||
|
var body = document.body
|
|||
|
|
|||
|
var VERSION = '3.3.0'
|
|||
|
|
|||
|
var ROUTINE = [
|
|||
|
// Initialise the condition with feature-detecting
|
|||
|
// classes (Modernizr-alike), binding onto the root
|
|||
|
// element, possibly `<html>`.
|
|||
|
'initCond',
|
|||
|
|
|||
|
// Address element normalisation
|
|||
|
'renderElem',
|
|||
|
|
|||
|
// Handle Biaodian
|
|||
|
/* 'jinzify', */
|
|||
|
'renderJiya',
|
|||
|
'renderHanging',
|
|||
|
|
|||
|
// Address Biaodian correction
|
|||
|
'correctBiaodian',
|
|||
|
|
|||
|
// Address Hanzi and Western script mixed spacing
|
|||
|
'renderHWS',
|
|||
|
|
|||
|
// Address presentational correction to combining ligatures
|
|||
|
'substCombLigaWithPUA'
|
|||
|
|
|||
|
// Address semantic correction to inaccurate characters
|
|||
|
// **Note:** inactivated by default
|
|||
|
/* 'substInaccurateChar', */
|
|||
|
]
|
|||
|
|
|||
|
// Define Han
|
|||
|
var Han = function( context, condition ) {
|
|||
|
return new Han.fn.init( context, condition )
|
|||
|
}
|
|||
|
|
|||
|
var init = function() {
|
|||
|
if ( arguments[ 0 ] ) {
|
|||
|
this.context = arguments[ 0 ]
|
|||
|
}
|
|||
|
if ( arguments[ 1 ] ) {
|
|||
|
this.condition = arguments[ 1 ]
|
|||
|
}
|
|||
|
return this
|
|||
|
}
|
|||
|
|
|||
|
Han.version = VERSION
|
|||
|
|
|||
|
Han.fn = Han.prototype = {
|
|||
|
version: VERSION,
|
|||
|
|
|||
|
constructor: Han,
|
|||
|
|
|||
|
// Body as the default target context
|
|||
|
context: body,
|
|||
|
|
|||
|
// Root element as the default condition
|
|||
|
condition: root,
|
|||
|
|
|||
|
// Default rendering routine
|
|||
|
routine: ROUTINE,
|
|||
|
|
|||
|
init: init,
|
|||
|
|
|||
|
setRoutine: function( routine ) {
|
|||
|
if ( Array.isArray( routine )) {
|
|||
|
this.routine = routine
|
|||
|
}
|
|||
|
return this
|
|||
|
},
|
|||
|
|
|||
|
// Note that the routine set up here will execute
|
|||
|
// only once. The method won't alter the routine in
|
|||
|
// the instance or in the prototype chain.
|
|||
|
render: function( routine ) {
|
|||
|
var it = this
|
|||
|
var routine = Array.isArray( routine )
|
|||
|
? routine
|
|||
|
: this.routine
|
|||
|
|
|||
|
routine
|
|||
|
.forEach(function( method ) {
|
|||
|
if (
|
|||
|
typeof method === 'string' &&
|
|||
|
typeof it[ method ] === 'function'
|
|||
|
) {
|
|||
|
it[ method ]()
|
|||
|
} else if (
|
|||
|
Array.isArray( method ) &&
|
|||
|
typeof it[ method[0] ] === 'function'
|
|||
|
) {
|
|||
|
it[ method.shift() ].apply( it, method )
|
|||
|
}
|
|||
|
})
|
|||
|
return this
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Han.fn.init.prototype = Han.fn
|
|||
|
|
|||
|
/**
|
|||
|
* Shortcut for `render()` under the default
|
|||
|
* situation.
|
|||
|
*
|
|||
|
* Once initialised, replace `Han.init` with the
|
|||
|
* instance for future usage.
|
|||
|
*/
|
|||
|
Han.init = function() {
|
|||
|
return Han.init = Han().render()
|
|||
|
}
|
|||
|
|
|||
|
var UNICODE = {
|
|||
|
/**
|
|||
|
* Western punctuation (西文標點符號)
|
|||
|
*/
|
|||
|
punct: {
|
|||
|
base: '[\u2026,.;:!?\u203D_]',
|
|||
|
sing: '[\u2010-\u2014\u2026]',
|
|||
|
middle: '[\\\/~\\-&\u2010-\u2014_]',
|
|||
|
open: '[\'"‘“\\(\\[\u00A1\u00BF\u2E18\u00AB\u2039\u201A\u201C\u201E]',
|
|||
|
close: '[\'"”’\\)\\]\u00BB\u203A\u201B\u201D\u201F]',
|
|||
|
end: '[\'"”’\\)\\]\u00BB\u203A\u201B\u201D\u201F\u203C\u203D\u2047-\u2049,.;:!?]',
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* CJK biaodian (CJK標點符號)
|
|||
|
*/
|
|||
|
biaodian: {
|
|||
|
base: '[︰.、,。:;?!ー]',
|
|||
|
liga: '[—…⋯]',
|
|||
|
middle: '[·\/-゠\uFF06\u30FB\uFF3F]',
|
|||
|
open: '[「『《〈(〔[{【〖]',
|
|||
|
close: '[」』》〉)〕]}】〗]',
|
|||
|
end: '[」』》〉)〕]}】〗︰.、,。:;?!ー]'
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* CJK-related blocks (CJK相關字符區段)
|
|||
|
*
|
|||
|
* 1. 中日韓統一意音文字:[\u4E00-\u9FFF]
|
|||
|
Basic CJK unified ideographs
|
|||
|
* 2. 擴展-A區:[\u3400-\u4DB5]
|
|||
|
Extended-A
|
|||
|
* 3. 擴展-B區:[\u20000-\u2A6D6]([\uD840-\uD869][\uDC00-\uDED6])
|
|||
|
Extended-B
|
|||
|
* 4. 擴展-C區:[\u2A700-\u2B734](\uD86D[\uDC00-\uDF3F]|[\uD86A-\uD86C][\uDC00-\uDFFF]|\uD869[\uDF00-\uDFFF])
|
|||
|
Extended-C
|
|||
|
* 5. 擴展-D區:[\u2B740-\u2B81D](急用漢字,\uD86D[\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1F])
|
|||
|
Extended-D
|
|||
|
* 6. 擴展-E區:[\u2B820-\u2F7FF](暫未支援)
|
|||
|
Extended-E (not supported yet)
|
|||
|
* 7. 擴展-F區(暫未支援)
|
|||
|
Extended-F (not supported yet)
|
|||
|
* 8. 筆畫區:[\u31C0-\u31E3]
|
|||
|
Strokes
|
|||
|
* 9. 意音數字「〇」:[\u3007]
|
|||
|
Ideographic number zero
|
|||
|
* 10. 相容意音文字及補充:[\uF900-\uFAFF][\u2F800-\u2FA1D](不使用)
|
|||
|
Compatibility ideograph and supplement (not supported)
|
|||
|
|
|||
|
12 exceptions:
|
|||
|
[\uFA0E\uFA0F\uFA11\uFA13\uFA14\uFA1F\uFA21\uFA23\uFA24\uFA27-\uFA29]
|
|||
|
|
|||
|
https://zh.wikipedia.org/wiki/中日韓統一表意文字#cite_note-1
|
|||
|
|
|||
|
* 11. 康熙字典及簡化字部首:[\u2F00-\u2FD5\u2E80-\u2EF3]
|
|||
|
Kangxi and supplement radicals
|
|||
|
* 12. 意音文字描述字元:[\u2FF0-\u2FFA]
|
|||
|
Ideographic description characters
|
|||
|
*/
|
|||
|
hanzi: {
|
|||
|
base: '[\u4E00-\u9FFF\u3400-\u4DB5\u31C0-\u31E3\u3007\uFA0E\uFA0F\uFA11\uFA13\uFA14\uFA1F\uFA21\uFA23\uFA24\uFA27-\uFA29]|[\uD800-\uDBFF][\uDC00-\uDFFF]',
|
|||
|
desc: '[\u2FF0-\u2FFA]',
|
|||
|
radical: '[\u2F00-\u2FD5\u2E80-\u2EF3]'
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Latin script blocks (拉丁字母區段)
|
|||
|
*
|
|||
|
* 1. 基本拉丁字母:A-Za-z
|
|||
|
Basic Latin
|
|||
|
* 2. 阿拉伯數字:0-9
|
|||
|
Digits
|
|||
|
* 3. 補充-1:[\u00C0-\u00FF]
|
|||
|
Latin-1 supplement
|
|||
|
* 4. 擴展-A區:[\u0100-\u017F]
|
|||
|
Extended-A
|
|||
|
* 5. 擴展-B區:[\u0180-\u024F]
|
|||
|
Extended-B
|
|||
|
* 5. 擴展-C區:[\u2C60-\u2C7F]
|
|||
|
Extended-C
|
|||
|
* 5. 擴展-D區:[\uA720-\uA7FF]
|
|||
|
Extended-D
|
|||
|
* 6. 附加區:[\u1E00-\u1EFF]
|
|||
|
Extended additional
|
|||
|
* 7. 變音組字符:[\u0300-\u0341\u1DC0-\u1DFF]
|
|||
|
Combining diacritical marks
|
|||
|
*/
|
|||
|
latin: {
|
|||
|
base: '[A-Za-z0-9\u00C0-\u00FF\u0100-\u017F\u0180-\u024F\u2C60-\u2C7F\uA720-\uA7FF\u1E00-\u1EFF]',
|
|||
|
combine: '[\u0300-\u0341\u1DC0-\u1DFF]'
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Elli̱niká (Greek) script blocks (希臘字母區段)
|
|||
|
*
|
|||
|
* 1. 希臘字母及擴展:[\u0370–\u03FF\u1F00-\u1FFF]
|
|||
|
Basic Greek & Greek Extended
|
|||
|
* 2. 阿拉伯數字:0-9
|
|||
|
Digits
|
|||
|
* 3. 希臘字母變音組字符:[\u0300-\u0345\u1DC0-\u1DFF]
|
|||
|
Combining diacritical marks
|
|||
|
*/
|
|||
|
ellinika: {
|
|||
|
base: '[0-9\u0370-\u03FF\u1F00-\u1FFF]',
|
|||
|
combine: '[\u0300-\u0345\u1DC0-\u1DFF]'
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Kirillica (Cyrillic) script blocks (西里爾字母區段)
|
|||
|
*
|
|||
|
* 1. 西里爾字母及補充:[\u0400-\u0482\u048A-\u04FF\u0500-\u052F]
|
|||
|
Basic Cyrillic and supplement
|
|||
|
* 2. 擴展B區:[\uA640-\uA66E\uA67E-\uA697]
|
|||
|
Extended-B
|
|||
|
* 3. 阿拉伯數字:0-9
|
|||
|
Digits
|
|||
|
* 4. 西里爾字母組字符:[\u0483-\u0489\u2DE0-\u2DFF\uA66F-\uA67D\uA69F](位擴展A、B區)
|
|||
|
Cyrillic combining diacritical marks (in extended-A, B)
|
|||
|
*/
|
|||
|
kirillica: {
|
|||
|
base: '[0-9\u0400-\u0482\u048A-\u04FF\u0500-\u052F\uA640-\uA66E\uA67E-\uA697]',
|
|||
|
combine: '[\u0483-\u0489\u2DE0-\u2DFF\uA66F-\uA67D\uA69F]'
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Kana (假名)
|
|||
|
*
|
|||
|
* 1. 日文假名:[\u30A2\u30A4\u30A6\u30A8\u30AA-\u30FA\u3042\u3044\u3046\u3048\u304A-\u3094\u309F\u30FF]
|
|||
|
Japanese Kana
|
|||
|
* 2. 假名補充[\u1B000\u1B001](\uD82C[\uDC00-\uDC01])
|
|||
|
Kana supplement
|
|||
|
* 3. 日文假名小寫:[\u3041\u3043\u3045\u3047\u3049\u30A1\u30A3\u30A5\u30A7\u30A9\u3063\u3083\u3085\u3087\u308E\u3095\u3096\u30C3\u30E3\u30E5\u30E7\u30EE\u30F5\u30F6\u31F0-\u31FF]
|
|||
|
Japanese small Kana
|
|||
|
* 4. 假名組字符:[\u3099-\u309C]
|
|||
|
Kana combining characters
|
|||
|
* 5. 半形假名:[\uFF66-\uFF9F]
|
|||
|
Halfwidth Kana
|
|||
|
* 6. 符號:[\u309D\u309E\u30FB-\u30FE]
|
|||
|
Marks
|
|||
|
*/
|
|||
|
kana: {
|
|||
|
base: '[\u30A2\u30A4\u30A6\u30A8\u30AA-\u30FA\u3042\u3044\u3046\u3048\u304A-\u3094\u309F\u30FF]|\uD82C[\uDC00-\uDC01]',
|
|||
|
small: '[\u3041\u3043\u3045\u3047\u3049\u30A1\u30A3\u30A5\u30A7\u30A9\u3063\u3083\u3085\u3087\u308E\u3095\u3096\u30C3\u30E3\u30E5\u30E7\u30EE\u30F5\u30F6\u31F0-\u31FF]',
|
|||
|
combine: '[\u3099-\u309C]',
|
|||
|
half: '[\uFF66-\uFF9F]',
|
|||
|
mark: '[\u30A0\u309D\u309E\u30FB-\u30FE]'
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Eonmun (Hangul, 諺文)
|
|||
|
*
|
|||
|
* 1. 諺文音節:[\uAC00-\uD7A3]
|
|||
|
Eonmun (Hangul) syllables
|
|||
|
* 2. 諺文字母:[\u1100-\u11FF\u314F-\u3163\u3131-\u318E\uA960-\uA97C\uD7B0-\uD7FB]
|
|||
|
Eonmun (Hangul) letters
|
|||
|
* 3. 半形諺文字母:[\uFFA1-\uFFDC]
|
|||
|
Halfwidth Eonmun (Hangul) letters
|
|||
|
*/
|
|||
|
eonmun: {
|
|||
|
base: '[\uAC00-\uD7A3]',
|
|||
|
letter: '[\u1100-\u11FF\u314F-\u3163\u3131-\u318E\uA960-\uA97C\uD7B0-\uD7FB]',
|
|||
|
half: '[\uFFA1-\uFFDC]'
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Zhuyin (注音符號, Mandarin & Dialect Phonetic Symbols)
|
|||
|
*
|
|||
|
* 1. 國語注音、方言音符號:[\u3105-\u312D][\u31A0-\u31BA]
|
|||
|
Bopomofo phonetic symbols
|
|||
|
* 2. 平上去聲調號:[\u02D9\u02CA\u02C5\u02C7\u02EA\u02EB\u02CB] (**註:**國語三聲包含乙個不合規範的符號)
|
|||
|
Level, rising, departing tones
|
|||
|
* 3. 入聲調號:[\u31B4-\u31B7][\u0358\u030d]?
|
|||
|
Checked (entering) tones
|
|||
|
*/
|
|||
|
zhuyin: {
|
|||
|
base: '[\u3105-\u312D\u31A0-\u31BA]',
|
|||
|
initial: '[\u3105-\u3119\u312A-\u312C\u31A0-\u31A3]',
|
|||
|
medial: '[\u3127-\u3129]',
|
|||
|
final: '[\u311A-\u3129\u312D\u31A4-\u31B3\u31B8-\u31BA]',
|
|||
|
tone: '[\u02D9\u02CA\u02C5\u02C7\u02CB\u02EA\u02EB]',
|
|||
|
checked: '[\u31B4-\u31B7][\u0358\u030d]?'
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
var TYPESET = (function() {
|
|||
|
var rWhite = '[\\x20\\t\\r\\n\\f]'
|
|||
|
// Whitespace characters
|
|||
|
// http://www.w3.org/TR/css3-selectors/#whitespace
|
|||
|
|
|||
|
var rPtOpen = UNICODE.punct.open
|
|||
|
var rPtClose = UNICODE.punct.close
|
|||
|
var rPtEnd = UNICODE.punct.end
|
|||
|
var rPtMid = UNICODE.punct.middle
|
|||
|
var rPtSing = UNICODE.punct.sing
|
|||
|
var rPt = rPtOpen + '|' + rPtEnd + '|' + rPtMid
|
|||
|
|
|||
|
var rBDOpen = UNICODE.biaodian.open
|
|||
|
var rBDClose = UNICODE.biaodian.close
|
|||
|
var rBDEnd = UNICODE.biaodian.end
|
|||
|
var rBDMid = UNICODE.biaodian.middle
|
|||
|
var rBDLiga = UNICODE.biaodian.liga + '{2}'
|
|||
|
var rBD = rBDOpen + '|' + rBDEnd + '|' + rBDMid
|
|||
|
|
|||
|
var rKana = UNICODE.kana.base + UNICODE.kana.combine + '?'
|
|||
|
var rKanaS = UNICODE.kana.small + UNICODE.kana.combine + '?'
|
|||
|
var rKanaH = UNICODE.kana.half
|
|||
|
var rEon = UNICODE.eonmun.base + '|' + UNICODE.eonmun.letter
|
|||
|
var rEonH = UNICODE.eonmun.half
|
|||
|
|
|||
|
var rHan = UNICODE.hanzi.base + '|' + UNICODE.hanzi.desc + '|' + UNICODE.hanzi.radical + '|' + rKana
|
|||
|
|
|||
|
var rCbn = UNICODE.ellinika.combine
|
|||
|
var rLatn = UNICODE.latin.base + rCbn + '*'
|
|||
|
var rGk = UNICODE.ellinika.base + rCbn + '*'
|
|||
|
|
|||
|
var rCyCbn = UNICODE.kirillica.combine
|
|||
|
var rCy = UNICODE.kirillica.base + rCyCbn + '*'
|
|||
|
|
|||
|
var rAlph = rLatn + '|' + rGk + '|' + rCy
|
|||
|
|
|||
|
// For words like `it's`, `Jones’s` or `'99`
|
|||
|
var rApo = '[\u0027\u2019]'
|
|||
|
var rChar = rHan + '|(?:' + rAlph + '|' + rApo + ')+'
|
|||
|
|
|||
|
var rZyS = UNICODE.zhuyin.initial
|
|||
|
var rZyJ = UNICODE.zhuyin.medial
|
|||
|
var rZyY = UNICODE.zhuyin.final
|
|||
|
var rZyD = UNICODE.zhuyin.tone + '|' + UNICODE.zhuyin.checked
|
|||
|
|
|||
|
return {
|
|||
|
/* Character-level selector (字級選擇器)
|
|||
|
*/
|
|||
|
char: {
|
|||
|
punct: {
|
|||
|
all: new RegExp( '(' + rPt + ')', 'g' ),
|
|||
|
open: new RegExp( '(' + rPtOpen + ')', 'g' ),
|
|||
|
end: new RegExp( '(' + rPtEnd + ')', 'g' ),
|
|||
|
sing: new RegExp( '(' + rPtSing + ')', 'g' )
|
|||
|
},
|
|||
|
|
|||
|
biaodian: {
|
|||
|
all: new RegExp( '(' + rBD + ')', 'g' ),
|
|||
|
open: new RegExp( '(' + rBDOpen + ')', 'g' ),
|
|||
|
close: new RegExp( '(' + rBDClose + ')', 'g' ),
|
|||
|
end: new RegExp( '(' + rBDEnd + ')', 'g' ),
|
|||
|
liga: new RegExp( '(' + rBDLiga + ')', 'g' )
|
|||
|
},
|
|||
|
|
|||
|
hanzi: new RegExp( '(' + rHan + ')', 'g' ),
|
|||
|
|
|||
|
latin: new RegExp( '(' + rLatn + ')', 'ig' ),
|
|||
|
ellinika: new RegExp( '(' + rGk + ')', 'ig' ),
|
|||
|
kirillica: new RegExp( '(' + rCy + ')', 'ig' ),
|
|||
|
|
|||
|
kana: new RegExp( '(' + rKana + '|' + rKanaS + '|' + rKanaH + ')', 'g' ),
|
|||
|
eonmun: new RegExp( '(' + rEon + '|' + rEonH + ')', 'g' )
|
|||
|
},
|
|||
|
|
|||
|
/* Word-level selectors (詞級選擇器)
|
|||
|
*/
|
|||
|
group: {
|
|||
|
biaodian: [
|
|||
|
new RegExp( '((' + rBD + '){2,})', 'g' ),
|
|||
|
new RegExp( '(' + rBDLiga + rBDOpen + ')', 'g' )
|
|||
|
],
|
|||
|
punct: null,
|
|||
|
hanzi: new RegExp( '(' + rHan + ')+', 'g' ),
|
|||
|
western: new RegExp( '(' + rLatn + '|' + rGk + '|' + rCy + '|' + rPt + ')+', 'ig' ),
|
|||
|
kana: new RegExp( '(' + rKana + '|' + rKanaS + '|' + rKanaH + ')+', 'g' ),
|
|||
|
eonmun: new RegExp( '(' + rEon + '|' + rEonH + '|' + rPt + ')+', 'g' )
|
|||
|
},
|
|||
|
|
|||
|
/* Punctuation Rules (禁則)
|
|||
|
*/
|
|||
|
jinze: {
|
|||
|
hanging: new RegExp( rWhite + '*([、,。.])(?!' + rBDEnd + ')', 'ig' ),
|
|||
|
touwei: new RegExp( '(' + rBDOpen + '+)(' + rChar + ')(' + rBDEnd + '+)', 'ig' ),
|
|||
|
tou: new RegExp( '(' + rBDOpen + '+)(' + rChar + ')', 'ig' ),
|
|||
|
wei: new RegExp( '(' + rChar + ')(' + rBDEnd + '+)', 'ig' ),
|
|||
|
middle: new RegExp( '(' + rChar + ')(' + rBDMid + ')(' + rChar + ')', 'ig' )
|
|||
|
},
|
|||
|
|
|||
|
zhuyin: {
|
|||
|
form: new RegExp( '^\u02D9?(' + rZyS + ')?(' + rZyJ + ')?(' + rZyY + ')?(' + rZyD + ')?$' ),
|
|||
|
diao: new RegExp( '(' + rZyD + ')', 'g' )
|
|||
|
},
|
|||
|
|
|||
|
/* Hanzi and Western mixed spacing (漢字西文混排間隙)
|
|||
|
* - Basic mode
|
|||
|
* - Strict mode
|
|||
|
*/
|
|||
|
hws: {
|
|||
|
base: [
|
|||
|
new RegExp( '('+ rHan + ')(' + rAlph + '|' + rPtOpen + ')', 'ig' ),
|
|||
|
new RegExp( '('+ rAlph + '|' + rPtEnd + ')(' + rHan + ')', 'ig' )
|
|||
|
],
|
|||
|
|
|||
|
strict: [
|
|||
|
new RegExp( '('+ rHan + ')' + rWhite + '?(' + rAlph + '|' + rPtOpen + ')', 'ig' ),
|
|||
|
new RegExp( '('+ rAlph + '|' + rPtEnd + ')' + rWhite + '?(' + rHan + ')', 'ig' )
|
|||
|
]
|
|||
|
},
|
|||
|
|
|||
|
// The feature displays the following characters
|
|||
|
// in its variant form for font consistency and
|
|||
|
// presentational reason. Meanwhile, this won't
|
|||
|
// alter the original character in the DOM.
|
|||
|
'display-as': {
|
|||
|
'ja-font-for-hant': [
|
|||
|
// '夠 够',
|
|||
|
'查 査',
|
|||
|
'啟 啓',
|
|||
|
'鄉 鄕',
|
|||
|
'值 値',
|
|||
|
'污 汚'
|
|||
|
],
|
|||
|
|
|||
|
'comb-liga-pua': [
|
|||
|
[ '\u0061[\u030d\u0358]', '\uDB80\uDC61' ],
|
|||
|
[ '\u0065[\u030d\u0358]', '\uDB80\uDC65' ],
|
|||
|
[ '\u0069[\u030d\u0358]', '\uDB80\uDC69' ],
|
|||
|
[ '\u006F[\u030d\u0358]', '\uDB80\uDC6F' ],
|
|||
|
[ '\u0075[\u030d\u0358]', '\uDB80\uDC75' ],
|
|||
|
|
|||
|
[ '\u31B4[\u030d\u0358]', '\uDB8C\uDDB4' ],
|
|||
|
[ '\u31B5[\u030d\u0358]', '\uDB8C\uDDB5' ],
|
|||
|
[ '\u31B6[\u030d\u0358]', '\uDB8C\uDDB6' ],
|
|||
|
[ '\u31B7[\u030d\u0358]', '\uDB8C\uDDB7' ]
|
|||
|
],
|
|||
|
|
|||
|
'comb-liga-vowel': [
|
|||
|
[ '\u0061[\u030d\u0358]', '\uDB80\uDC61' ],
|
|||
|
[ '\u0065[\u030d\u0358]', '\uDB80\uDC65' ],
|
|||
|
[ '\u0069[\u030d\u0358]', '\uDB80\uDC69' ],
|
|||
|
[ '\u006F[\u030d\u0358]', '\uDB80\uDC6F' ],
|
|||
|
[ '\u0075[\u030d\u0358]', '\uDB80\uDC75' ]
|
|||
|
],
|
|||
|
|
|||
|
'comb-liga-zhuyin': [
|
|||
|
[ '\u31B4[\u030d\u0358]', '\uDB8C\uDDB4' ],
|
|||
|
[ '\u31B5[\u030d\u0358]', '\uDB8C\uDDB5' ],
|
|||
|
[ '\u31B6[\u030d\u0358]', '\uDB8C\uDDB6' ],
|
|||
|
[ '\u31B7[\u030d\u0358]', '\uDB8C\uDDB7' ]
|
|||
|
]
|
|||
|
},
|
|||
|
|
|||
|
// The feature actually *converts* the character
|
|||
|
// in the DOM for semantic reason.
|
|||
|
//
|
|||
|
// Note that this could be aggressive.
|
|||
|
'inaccurate-char': [
|
|||
|
[ '[\u2022\u2027]', '\u00B7' ],
|
|||
|
[ '\u22EF\u22EF', '\u2026\u2026' ],
|
|||
|
[ '\u2500\u2500', '\u2014\u2014' ],
|
|||
|
[ '\u2035', '\u2018' ],
|
|||
|
[ '\u2032', '\u2019' ],
|
|||
|
[ '\u2036', '\u201C' ],
|
|||
|
[ '\u2033', '\u201D' ]
|
|||
|
]
|
|||
|
}
|
|||
|
})()
|
|||
|
|
|||
|
Han.UNICODE = UNICODE
|
|||
|
Han.TYPESET = TYPESET
|
|||
|
|
|||
|
// Aliases
|
|||
|
Han.UNICODE.cjk = Han.UNICODE.hanzi
|
|||
|
Han.UNICODE.greek = Han.UNICODE.ellinika
|
|||
|
Han.UNICODE.cyrillic = Han.UNICODE.kirillica
|
|||
|
Han.UNICODE.hangul = Han.UNICODE.eonmun
|
|||
|
Han.UNICODE.zhuyin.ruyun = Han.UNICODE.zhuyin.checked
|
|||
|
|
|||
|
Han.TYPESET.char.cjk = Han.TYPESET.char.hanzi
|
|||
|
Han.TYPESET.char.greek = Han.TYPESET.char.ellinika
|
|||
|
Han.TYPESET.char.cyrillic = Han.TYPESET.char.kirillica
|
|||
|
Han.TYPESET.char.hangul = Han.TYPESET.char.eonmun
|
|||
|
|
|||
|
Han.TYPESET.group.hangul = Han.TYPESET.group.eonmun
|
|||
|
Han.TYPESET.group.cjk = Han.TYPESET.group.hanzi
|
|||
|
|
|||
|
var $ = {
|
|||
|
/**
|
|||
|
* Query selectors which return arrays of the resulted
|
|||
|
* node lists.
|
|||
|
*/
|
|||
|
id: function( selector, $context ) {
|
|||
|
return ( $context || document ).getElementById( selector )
|
|||
|
},
|
|||
|
|
|||
|
tag: function( selector, $context ) {
|
|||
|
return this.makeArray(
|
|||
|
( $context || document ).getElementsByTagName( selector )
|
|||
|
)
|
|||
|
},
|
|||
|
|
|||
|
qs: function( selector, $context ) {
|
|||
|
return ( $context || document ).querySelector( selector )
|
|||
|
},
|
|||
|
|
|||
|
qsa: function( selector, $context ) {
|
|||
|
return this.makeArray(
|
|||
|
( $context || document ).querySelectorAll( selector )
|
|||
|
)
|
|||
|
},
|
|||
|
|
|||
|
parent: function( $node, selector ) {
|
|||
|
return selector
|
|||
|
? (function() {
|
|||
|
if ( typeof $.matches !== 'function' ) return
|
|||
|
|
|||
|
while (!$.matches( $node, selector )) {
|
|||
|
if (
|
|||
|
!$node ||
|
|||
|
$node === document.documentElement
|
|||
|
) {
|
|||
|
$node = undefined
|
|||
|
break
|
|||
|
}
|
|||
|
$node = $node.parentNode
|
|||
|
}
|
|||
|
return $node
|
|||
|
})()
|
|||
|
: $node
|
|||
|
? $node.parentNode : undefined
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Create a document fragment, a text node with text
|
|||
|
* or an element with/without classes.
|
|||
|
*/
|
|||
|
create: function( name, clazz ) {
|
|||
|
var $elmt = '!' === name
|
|||
|
? document.createDocumentFragment()
|
|||
|
: '' === name
|
|||
|
? document.createTextNode( clazz || '' )
|
|||
|
: document.createElement( name )
|
|||
|
|
|||
|
try {
|
|||
|
if ( clazz ) {
|
|||
|
$elmt.className = clazz
|
|||
|
}
|
|||
|
} catch (e) {}
|
|||
|
|
|||
|
return $elmt
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Clone a DOM node (text, element or fragment) deeply
|
|||
|
* or childlessly.
|
|||
|
*/
|
|||
|
clone: function( $node, deep ) {
|
|||
|
return $node.cloneNode(
|
|||
|
typeof deep === 'boolean'
|
|||
|
? deep
|
|||
|
: true
|
|||
|
)
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Remove a node (text, element or fragment).
|
|||
|
*/
|
|||
|
remove: function( $node ) {
|
|||
|
return $node.parentNode.removeChild( $node )
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Set attributes all in once with an object.
|
|||
|
*/
|
|||
|
setAttr: function( target, attr ) {
|
|||
|
if ( typeof attr !== 'object' ) return
|
|||
|
var len = attr.length
|
|||
|
|
|||
|
// Native `NamedNodeMap``:
|
|||
|
if (
|
|||
|
typeof attr[0] === 'object' &&
|
|||
|
'name' in attr[0]
|
|||
|
) {
|
|||
|
for ( var i = 0; i < len; i++ ) {
|
|||
|
if ( attr[ i ].value !== undefined ) {
|
|||
|
target.setAttribute( attr[ i ].name, attr[ i ].value )
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Plain object:
|
|||
|
} else {
|
|||
|
for ( var name in attr ) {
|
|||
|
if (
|
|||
|
attr.hasOwnProperty( name ) &&
|
|||
|
attr[ name ] !== undefined
|
|||
|
) {
|
|||
|
target.setAttribute( name, attr[ name ] )
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
return target
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Indicate whether or not the given node is an
|
|||
|
* element.
|
|||
|
*/
|
|||
|
isElmt: function( $node ) {
|
|||
|
return $node && $node.nodeType === Node.ELEMENT_NODE
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Indicate whether or not the given node should
|
|||
|
* be ignored (`<wbr>` or comments).
|
|||
|
*/
|
|||
|
isIgnorable: function( $node ) {
|
|||
|
if ( !$node ) return false
|
|||
|
|
|||
|
return (
|
|||
|
$node.nodeName === 'WBR' ||
|
|||
|
$node.nodeType === Node.COMMENT_NODE
|
|||
|
)
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Convert array-like objects into real arrays.
|
|||
|
*/
|
|||
|
makeArray: function( object ) {
|
|||
|
return Array.prototype.slice.call( object )
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Extend target with an object.
|
|||
|
*/
|
|||
|
extend: function( target, object ) {
|
|||
|
if ((
|
|||
|
typeof target === 'object' ||
|
|||
|
typeof target === 'function' ) &&
|
|||
|
typeof object === 'object'
|
|||
|
) {
|
|||
|
for ( var name in object ) {
|
|||
|
if (object.hasOwnProperty( name )) {
|
|||
|
target[ name ] = object[ name ]
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
return target
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
var Fibre =
|
|||
|
/*!
|
|||
|
* Fibre.js v0.2.1 | MIT License | github.com/ethantw/fibre.js
|
|||
|
* Based on findAndReplaceDOMText
|
|||
|
*/
|
|||
|
|
|||
|
function( Finder ) {
|
|||
|
|
|||
|
'use strict'
|
|||
|
|
|||
|
var VERSION = '0.2.1'
|
|||
|
var NON_INLINE_PROSE = Finder.NON_INLINE_PROSE
|
|||
|
var AVOID_NON_PROSE = Finder.PRESETS.prose.filterElements
|
|||
|
|
|||
|
var global = window || {}
|
|||
|
var document = global.document || undefined
|
|||
|
|
|||
|
function matches( node, selector, bypassNodeType39 ) {
|
|||
|
var Efn = Element.prototype
|
|||
|
var matches = Efn.matches || Efn.mozMatchesSelector || Efn.msMatchesSelector || Efn.webkitMatchesSelector
|
|||
|
|
|||
|
if ( node instanceof Element ) {
|
|||
|
return matches.call( node, selector )
|
|||
|
} else if ( bypassNodeType39 ) {
|
|||
|
if ( /^[39]$/.test( node.nodeType )) return true
|
|||
|
}
|
|||
|
return false
|
|||
|
}
|
|||
|
|
|||
|
if ( typeof document === 'undefined' ) throw new Error( 'Fibre requires a DOM-supported environment.' )
|
|||
|
|
|||
|
var Fibre = function( context, preset ) {
|
|||
|
return new Fibre.fn.init( context, preset )
|
|||
|
}
|
|||
|
|
|||
|
Fibre.version = VERSION
|
|||
|
Fibre.matches = matches
|
|||
|
|
|||
|
Fibre.fn = Fibre.prototype = {
|
|||
|
constructor: Fibre,
|
|||
|
|
|||
|
version: VERSION,
|
|||
|
|
|||
|
finder: [],
|
|||
|
|
|||
|
context: undefined,
|
|||
|
|
|||
|
portionMode: 'retain',
|
|||
|
|
|||
|
selector: {},
|
|||
|
|
|||
|
preset: 'prose',
|
|||
|
|
|||
|
init: function( context, noPreset ) {
|
|||
|
if ( !!noPreset ) this.preset = null
|
|||
|
|
|||
|
this.selector = {
|
|||
|
context: null,
|
|||
|
filter: [],
|
|||
|
avoid: [],
|
|||
|
boundary: []
|
|||
|
}
|
|||
|
|
|||
|
if ( !context ) {
|
|||
|
throw new Error( 'A context is required for Fibre to initialise.' )
|
|||
|
} else if ( context instanceof Node ) {
|
|||
|
if ( context instanceof Document ) this.context = context.body || context
|
|||
|
else this.context = context
|
|||
|
} else if ( typeof context === 'string' ) {
|
|||
|
this.context = document.querySelector( context )
|
|||
|
this.selector.context = context
|
|||
|
}
|
|||
|
return this
|
|||
|
},
|
|||
|
|
|||
|
filterFn: function( node ) {
|
|||
|
var filter = this.selector.filter.join( ', ' ) || '*'
|
|||
|
var avoid = this.selector.avoid.join( ', ' ) || null
|
|||
|
var result = matches( node, filter, true ) && !matches( node, avoid )
|
|||
|
return ( this.preset === 'prose' ) ? AVOID_NON_PROSE( node ) && result : result
|
|||
|
},
|
|||
|
|
|||
|
boundaryFn: function( node ) {
|
|||
|
var boundary = this.selector.boundary.join( ', ' ) || null
|
|||
|
var result = matches( node, boundary )
|
|||
|
return ( this.preset === 'prose' ) ? NON_INLINE_PROSE( node ) || result : result
|
|||
|
},
|
|||
|
|
|||
|
filter: function( selector ) {
|
|||
|
if ( typeof selector === 'string' ) {
|
|||
|
this.selector.filter.push( selector )
|
|||
|
}
|
|||
|
return this
|
|||
|
},
|
|||
|
|
|||
|
endFilter: function( all ) {
|
|||
|
if ( all ) {
|
|||
|
this.selector.filter = []
|
|||
|
} else {
|
|||
|
this.selector.filter.pop()
|
|||
|
}
|
|||
|
return this
|
|||
|
},
|
|||
|
|
|||
|
avoid: function( selector ) {
|
|||
|
if ( typeof selector === 'string' ) {
|
|||
|
this.selector.avoid.push( selector )
|
|||
|
}
|
|||
|
return this
|
|||
|
},
|
|||
|
|
|||
|
endAvoid: function( all ) {
|
|||
|
if ( all ) {
|
|||
|
this.selector.avoid = []
|
|||
|
} else {
|
|||
|
this.selector.avoid.pop()
|
|||
|
}
|
|||
|
return this
|
|||
|
},
|
|||
|
|
|||
|
addBoundary: function( selector ) {
|
|||
|
if ( typeof selector === 'string' ) {
|
|||
|
this.selector.boundary.push( selector )
|
|||
|
}
|
|||
|
return this
|
|||
|
},
|
|||
|
|
|||
|
removeBoundary: function() {
|
|||
|
this.selector.boundary = []
|
|||
|
return this
|
|||
|
},
|
|||
|
|
|||
|
setMode: function( portionMode ) {
|
|||
|
this.portionMode = portionMode === 'first' ? 'first' : 'retain'
|
|||
|
return this
|
|||
|
},
|
|||
|
|
|||
|
replace: function( regexp, newSubStr ) {
|
|||
|
var it = this
|
|||
|
it.finder.push(Finder( it.context, {
|
|||
|
find: regexp,
|
|||
|
replace: newSubStr,
|
|||
|
filterElements: function( currentNode ) {
|
|||
|
return it.filterFn( currentNode )
|
|||
|
},
|
|||
|
forceContext: function( currentNode ) {
|
|||
|
return it.boundaryFn( currentNode )
|
|||
|
},
|
|||
|
portionMode: it.portionMode
|
|||
|
}))
|
|||
|
return it
|
|||
|
},
|
|||
|
|
|||
|
wrap: function( regexp, strElemName ) {
|
|||
|
var it = this
|
|||
|
it.finder.push(Finder( it.context, {
|
|||
|
find: regexp,
|
|||
|
wrap: strElemName,
|
|||
|
filterElements: function( currentNode ) {
|
|||
|
return it.filterFn( currentNode )
|
|||
|
},
|
|||
|
forceContext: function( currentNode ) {
|
|||
|
return it.boundaryFn( currentNode )
|
|||
|
},
|
|||
|
portionMode: it.portionMode
|
|||
|
}))
|
|||
|
return it
|
|||
|
},
|
|||
|
|
|||
|
revert: function( level ) {
|
|||
|
var max = this.finder.length
|
|||
|
var level = Number( level ) || ( level === 0 ? Number(0) :
|
|||
|
( level === 'all' ? max : 1 ))
|
|||
|
|
|||
|
if ( typeof max === 'undefined' || max === 0 ) return this
|
|||
|
else if ( level > max ) level = max
|
|||
|
|
|||
|
for ( var i = level; i > 0; i-- ) {
|
|||
|
this.finder.pop().revert()
|
|||
|
}
|
|||
|
return this
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Deprecated API(s)
|
|||
|
Fibre.fn.filterOut = Fibre.fn.avoid
|
|||
|
|
|||
|
// Make sure init() inherit from Fibre()
|
|||
|
Fibre.fn.init.prototype = Fibre.fn
|
|||
|
|
|||
|
return Fibre
|
|||
|
|
|||
|
}(
|
|||
|
|
|||
|
/**
|
|||
|
* findAndReplaceDOMText v 0.4.3
|
|||
|
* @author James Padolsey http://james.padolsey.com
|
|||
|
* @license http://unlicense.org/UNLICENSE
|
|||
|
*
|
|||
|
* Matches the text of a DOM node against a regular expression
|
|||
|
* and replaces each match (or node-separated portions of the match)
|
|||
|
* in the specified element.
|
|||
|
*/
|
|||
|
(function() {
|
|||
|
|
|||
|
var PORTION_MODE_RETAIN = 'retain'
|
|||
|
var PORTION_MODE_FIRST = 'first'
|
|||
|
var doc = document
|
|||
|
var toString = {}.toString
|
|||
|
var hasOwn = {}.hasOwnProperty
|
|||
|
function isArray(a) {
|
|||
|
return toString.call(a) == '[object Array]'
|
|||
|
}
|
|||
|
|
|||
|
function escapeRegExp(s) {
|
|||
|
return String(s).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1')
|
|||
|
}
|
|||
|
|
|||
|
function exposed() {
|
|||
|
// Try deprecated arg signature first:
|
|||
|
return deprecated.apply(null, arguments) || findAndReplaceDOMText.apply(null, arguments)
|
|||
|
}
|
|||
|
|
|||
|
function deprecated(regex, node, replacement, captureGroup, elFilter) {
|
|||
|
if ((node && !node.nodeType) && arguments.length <= 2) {
|
|||
|
return false
|
|||
|
}
|
|||
|
var isReplacementFunction = typeof replacement == 'function'
|
|||
|
if (isReplacementFunction) {
|
|||
|
replacement = (function(original) {
|
|||
|
return function(portion, match) {
|
|||
|
return original(portion.text, match.startIndex)
|
|||
|
}
|
|||
|
}(replacement))
|
|||
|
}
|
|||
|
|
|||
|
// Awkward support for deprecated argument signature (<0.4.0)
|
|||
|
var instance = findAndReplaceDOMText(node, {
|
|||
|
|
|||
|
find: regex,
|
|||
|
|
|||
|
wrap: isReplacementFunction ? null : replacement,
|
|||
|
replace: isReplacementFunction ? replacement : '$' + (captureGroup || '&'),
|
|||
|
|
|||
|
prepMatch: function(m, mi) {
|
|||
|
|
|||
|
// Support captureGroup (a deprecated feature)
|
|||
|
|
|||
|
if (!m[0]) throw 'findAndReplaceDOMText cannot handle zero-length matches'
|
|||
|
if (captureGroup > 0) {
|
|||
|
var cg = m[captureGroup]
|
|||
|
m.index += m[0].indexOf(cg)
|
|||
|
m[0] = cg
|
|||
|
}
|
|||
|
|
|||
|
m.endIndex = m.index + m[0].length
|
|||
|
m.startIndex = m.index
|
|||
|
m.index = mi
|
|||
|
return m
|
|||
|
},
|
|||
|
filterElements: elFilter
|
|||
|
})
|
|||
|
exposed.revert = function() {
|
|||
|
return instance.revert()
|
|||
|
}
|
|||
|
return true
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* findAndReplaceDOMText
|
|||
|
*
|
|||
|
* Locates matches and replaces with replacementNode
|
|||
|
*
|
|||
|
* @param {Node} node Element or Text node to search within
|
|||
|
* @param {RegExp} options.find The regular expression to match
|
|||
|
* @param {String|Element} [options.wrap] A NodeName, or a Node to clone
|
|||
|
* @param {String|Function} [options.replace='$&'] What to replace each match with
|
|||
|
* @param {Function} [options.filterElements] A Function to be called to check whether to
|
|||
|
* process an element. (returning true = process element,
|
|||
|
* returning false = avoid element)
|
|||
|
*/
|
|||
|
function findAndReplaceDOMText(node, options) {
|
|||
|
return new Finder(node, options)
|
|||
|
}
|
|||
|
|
|||
|
exposed.NON_PROSE_ELEMENTS = {
|
|||
|
br:1, hr:1,
|
|||
|
// Media / Source elements:
|
|||
|
script:1, style:1, img:1, video:1, audio:1, canvas:1, svg:1, map:1, object:1,
|
|||
|
// Input elements
|
|||
|
input:1, textarea:1, select:1, option:1, optgroup: 1, button:1
|
|||
|
}
|
|||
|
exposed.NON_CONTIGUOUS_PROSE_ELEMENTS = {
|
|||
|
|
|||
|
// Elements that will not contain prose or block elements where we don't
|
|||
|
// want prose to be matches across element borders:
|
|||
|
|
|||
|
// Block Elements
|
|||
|
address:1, article:1, aside:1, blockquote:1, dd:1, div:1,
|
|||
|
dl:1, fieldset:1, figcaption:1, figure:1, footer:1, form:1, h1:1, h2:1, h3:1,
|
|||
|
h4:1, h5:1, h6:1, header:1, hgroup:1, hr:1, main:1, nav:1, noscript:1, ol:1,
|
|||
|
output:1, p:1, pre:1, section:1, ul:1,
|
|||
|
// Other misc. elements that are not part of continuous inline prose:
|
|||
|
br:1, li: 1, summary: 1, dt:1, details:1, rp:1, rt:1, rtc:1,
|
|||
|
// Media / Source elements:
|
|||
|
script:1, style:1, img:1, video:1, audio:1, canvas:1, svg:1, map:1, object:1,
|
|||
|
// Input elements
|
|||
|
input:1, textarea:1, select:1, option:1, optgroup: 1, button:1,
|
|||
|
// Table related elements:
|
|||
|
table:1, tbody:1, thead:1, th:1, tr:1, td:1, caption:1, col:1, tfoot:1, colgroup:1
|
|||
|
|
|||
|
}
|
|||
|
exposed.NON_INLINE_PROSE = function(el) {
|
|||
|
return hasOwn.call(exposed.NON_CONTIGUOUS_PROSE_ELEMENTS, el.nodeName.toLowerCase())
|
|||
|
}
|
|||
|
// Presets accessed via `options.preset` when calling findAndReplaceDOMText():
|
|||
|
exposed.PRESETS = {
|
|||
|
prose: {
|
|||
|
forceContext: exposed.NON_INLINE_PROSE,
|
|||
|
filterElements: function(el) {
|
|||
|
return !hasOwn.call(exposed.NON_PROSE_ELEMENTS, el.nodeName.toLowerCase())
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
exposed.Finder = Finder
|
|||
|
/**
|
|||
|
* Finder -- encapsulates logic to find and replace.
|
|||
|
*/
|
|||
|
function Finder(node, options) {
|
|||
|
|
|||
|
var preset = options.preset && exposed.PRESETS[options.preset]
|
|||
|
options.portionMode = options.portionMode || PORTION_MODE_RETAIN
|
|||
|
if (preset) {
|
|||
|
for (var i in preset) {
|
|||
|
if (hasOwn.call(preset, i) && !hasOwn.call(options, i)) {
|
|||
|
options[i] = preset[i]
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
this.node = node
|
|||
|
this.options = options
|
|||
|
// ENable match-preparation method to be passed as option:
|
|||
|
this.prepMatch = options.prepMatch || this.prepMatch
|
|||
|
this.reverts = []
|
|||
|
this.matches = this.search()
|
|||
|
if (this.matches.length) {
|
|||
|
this.processMatches()
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
Finder.prototype = {
|
|||
|
|
|||
|
/**
|
|||
|
* Searches for all matches that comply with the instance's 'match' option
|
|||
|
*/
|
|||
|
search: function() {
|
|||
|
|
|||
|
var match
|
|||
|
var matchIndex = 0
|
|||
|
var offset = 0
|
|||
|
var regex = this.options.find
|
|||
|
var textAggregation = this.getAggregateText()
|
|||
|
var matches = []
|
|||
|
var self = this
|
|||
|
regex = typeof regex === 'string' ? RegExp(escapeRegExp(regex), 'g') : regex
|
|||
|
matchAggregation(textAggregation)
|
|||
|
function matchAggregation(textAggregation) {
|
|||
|
for (var i = 0, l = textAggregation.length; i < l; ++i) {
|
|||
|
|
|||
|
var text = textAggregation[i]
|
|||
|
if (typeof text !== 'string') {
|
|||
|
// Deal with nested contexts: (recursive)
|
|||
|
matchAggregation(text)
|
|||
|
continue
|
|||
|
}
|
|||
|
|
|||
|
if (regex.global) {
|
|||
|
while (match = regex.exec(text)) {
|
|||
|
matches.push(self.prepMatch(match, matchIndex++, offset))
|
|||
|
}
|
|||
|
} else {
|
|||
|
if (match = text.match(regex)) {
|
|||
|
matches.push(self.prepMatch(match, 0, offset))
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
offset += text.length
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return matches
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Prepares a single match with useful meta info:
|
|||
|
*/
|
|||
|
prepMatch: function(match, matchIndex, characterOffset) {
|
|||
|
|
|||
|
if (!match[0]) {
|
|||
|
throw new Error('findAndReplaceDOMText cannot handle zero-length matches')
|
|||
|
}
|
|||
|
|
|||
|
match.endIndex = characterOffset + match.index + match[0].length
|
|||
|
match.startIndex = characterOffset + match.index
|
|||
|
match.index = matchIndex
|
|||
|
return match
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Gets aggregate text within subject node
|
|||
|
*/
|
|||
|
getAggregateText: function() {
|
|||
|
|
|||
|
var elementFilter = this.options.filterElements
|
|||
|
var forceContext = this.options.forceContext
|
|||
|
return getText(this.node)
|
|||
|
/**
|
|||
|
* Gets aggregate text of a node without resorting
|
|||
|
* to broken innerText/textContent
|
|||
|
*/
|
|||
|
function getText(node, txt) {
|
|||
|
|
|||
|
if (node.nodeType === 3) {
|
|||
|
return [node.data]
|
|||
|
}
|
|||
|
|
|||
|
if (elementFilter && !elementFilter(node)) {
|
|||
|
return []
|
|||
|
}
|
|||
|
|
|||
|
var txt = ['']
|
|||
|
var i = 0
|
|||
|
if (node = node.firstChild) do {
|
|||
|
|
|||
|
if (node.nodeType === 3) {
|
|||
|
txt[i] += node.data
|
|||
|
continue
|
|||
|
}
|
|||
|
|
|||
|
var innerText = getText(node)
|
|||
|
if (
|
|||
|
forceContext &&
|
|||
|
node.nodeType === 1 &&
|
|||
|
(forceContext === true || forceContext(node))
|
|||
|
) {
|
|||
|
txt[++i] = innerText
|
|||
|
txt[++i] = ''
|
|||
|
} else {
|
|||
|
if (typeof innerText[0] === 'string') {
|
|||
|
// Bridge nested text-node data so that they're
|
|||
|
// not considered their own contexts:
|
|||
|
// I.e. ['some', ['thing']] -> ['something']
|
|||
|
txt[i] += innerText.shift()
|
|||
|
}
|
|||
|
if (innerText.length) {
|
|||
|
txt[++i] = innerText
|
|||
|
txt[++i] = ''
|
|||
|
}
|
|||
|
}
|
|||
|
} while (node = node.nextSibling)
|
|||
|
return txt
|
|||
|
}
|
|||
|
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Steps through the target node, looking for matches, and
|
|||
|
* calling replaceFn when a match is found.
|
|||
|
*/
|
|||
|
processMatches: function() {
|
|||
|
|
|||
|
var matches = this.matches
|
|||
|
var node = this.node
|
|||
|
var elementFilter = this.options.filterElements
|
|||
|
var startPortion,
|
|||
|
endPortion,
|
|||
|
innerPortions = [],
|
|||
|
curNode = node,
|
|||
|
match = matches.shift(),
|
|||
|
atIndex = 0, // i.e. nodeAtIndex
|
|||
|
matchIndex = 0,
|
|||
|
portionIndex = 0,
|
|||
|
doAvoidNode,
|
|||
|
nodeStack = [node]
|
|||
|
out: while (true) {
|
|||
|
|
|||
|
if (curNode.nodeType === 3) {
|
|||
|
|
|||
|
if (!endPortion && curNode.length + atIndex >= match.endIndex) {
|
|||
|
|
|||
|
// We've found the ending
|
|||
|
endPortion = {
|
|||
|
node: curNode,
|
|||
|
index: portionIndex++,
|
|||
|
text: curNode.data.substring(match.startIndex - atIndex, match.endIndex - atIndex),
|
|||
|
indexInMatch: atIndex - match.startIndex,
|
|||
|
indexInNode: match.startIndex - atIndex, // always zero for end-portions
|
|||
|
endIndexInNode: match.endIndex - atIndex,
|
|||
|
isEnd: true
|
|||
|
}
|
|||
|
} else if (startPortion) {
|
|||
|
// Intersecting node
|
|||
|
innerPortions.push({
|
|||
|
node: curNode,
|
|||
|
index: portionIndex++,
|
|||
|
text: curNode.data,
|
|||
|
indexInMatch: atIndex - match.startIndex,
|
|||
|
indexInNode: 0 // always zero for inner-portions
|
|||
|
})
|
|||
|
}
|
|||
|
|
|||
|
if (!startPortion && curNode.length + atIndex > match.startIndex) {
|
|||
|
// We've found the match start
|
|||
|
startPortion = {
|
|||
|
node: curNode,
|
|||
|
index: portionIndex++,
|
|||
|
indexInMatch: 0,
|
|||
|
indexInNode: match.startIndex - atIndex,
|
|||
|
endIndexInNode: match.endIndex - atIndex,
|
|||
|
text: curNode.data.substring(match.startIndex - atIndex, match.endIndex - atIndex)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
atIndex += curNode.data.length
|
|||
|
}
|
|||
|
|
|||
|
doAvoidNode = curNode.nodeType === 1 && elementFilter && !elementFilter(curNode)
|
|||
|
if (startPortion && endPortion) {
|
|||
|
|
|||
|
curNode = this.replaceMatch(match, startPortion, innerPortions, endPortion)
|
|||
|
// processMatches has to return the node that replaced the endNode
|
|||
|
// and then we step back so we can continue from the end of the
|
|||
|
// match:
|
|||
|
|
|||
|
atIndex -= (endPortion.node.data.length - endPortion.endIndexInNode)
|
|||
|
startPortion = null
|
|||
|
endPortion = null
|
|||
|
innerPortions = []
|
|||
|
match = matches.shift()
|
|||
|
portionIndex = 0
|
|||
|
matchIndex++
|
|||
|
if (!match) {
|
|||
|
break; // no more matches
|
|||
|
}
|
|||
|
|
|||
|
} else if (
|
|||
|
!doAvoidNode &&
|
|||
|
(curNode.firstChild || curNode.nextSibling)
|
|||
|
) {
|
|||
|
// Move down or forward:
|
|||
|
if (curNode.firstChild) {
|
|||
|
nodeStack.push(curNode)
|
|||
|
curNode = curNode.firstChild
|
|||
|
} else {
|
|||
|
curNode = curNode.nextSibling
|
|||
|
}
|
|||
|
continue
|
|||
|
}
|
|||
|
|
|||
|
// Move forward or up:
|
|||
|
while (true) {
|
|||
|
if (curNode.nextSibling) {
|
|||
|
curNode = curNode.nextSibling
|
|||
|
break
|
|||
|
}
|
|||
|
curNode = nodeStack.pop()
|
|||
|
if (curNode === node) {
|
|||
|
break out
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
|
|||
|
},
|
|||
|
|
|||
|
/**
|
|||
|
* Reverts ... TODO
|
|||
|
*/
|
|||
|
revert: function() {
|
|||
|
// Reversion occurs backwards so as to avoid nodes subsequently
|
|||
|
// replaced during the matching phase (a forward process):
|
|||
|
for (var l = this.reverts.length; l--;) {
|
|||
|
this.reverts[l]()
|
|||
|
}
|
|||
|
this.reverts = []
|
|||
|
},
|
|||
|
|
|||
|
prepareReplacementString: function(string, portion, match, matchIndex) {
|
|||
|
var portionMode = this.options.portionMode
|
|||
|
if (
|
|||
|
portionMode === PORTION_MODE_FIRST &&
|
|||
|
portion.indexInMatch > 0
|
|||
|
) {
|
|||
|
return ''
|
|||
|
}
|
|||
|
string = string.replace(/\$(\d+|&|`|')/g, function($0, t) {
|
|||
|
var replacement
|
|||
|
switch(t) {
|
|||
|
case '&':
|
|||
|
replacement = match[0]
|
|||
|
break
|
|||
|
case '`':
|
|||
|
replacement = match.input.substring(0, match.startIndex)
|
|||
|
break
|
|||
|
case '\'':
|
|||
|
replacement = match.input.substring(match.endIndex)
|
|||
|
break
|
|||
|
default:
|
|||
|
replacement = match[+t]
|
|||
|
}
|
|||
|
return replacement
|
|||
|
})
|
|||
|
if (portionMode === PORTION_MODE_FIRST) {
|
|||
|
return string
|
|||
|
}
|
|||
|
|
|||
|
if (portion.isEnd) {
|
|||
|
return string.substring(portion.indexInMatch)
|
|||
|
}
|
|||
|
|
|||
|
return string.substring(portion.indexInMatch, portion.indexInMatch + portion.text.length)
|
|||
|
},
|
|||
|
|
|||
|
getPortionReplacementNode: function(portion, match, matchIndex) {
|
|||
|
|
|||
|
var replacement = this.options.replace || '$&'
|
|||
|
var wrapper = this.options.wrap
|
|||
|
if (wrapper && wrapper.nodeType) {
|
|||
|
// Wrapper has been provided as a stencil-node for us to clone:
|
|||
|
var clone = doc.createElement('div')
|
|||
|
clone.innerHTML = wrapper.outerHTML || new XMLSerializer().serializeToString(wrapper)
|
|||
|
wrapper = clone.firstChild
|
|||
|
}
|
|||
|
|
|||
|
if (typeof replacement == 'function') {
|
|||
|
replacement = replacement(portion, match, matchIndex)
|
|||
|
if (replacement && replacement.nodeType) {
|
|||
|
return replacement
|
|||
|
}
|
|||
|
return doc.createTextNode(String(replacement))
|
|||
|
}
|
|||
|
|
|||
|
var el = typeof wrapper == 'string' ? doc.createElement(wrapper) : wrapper
|
|||
|
replacement = doc.createTextNode(
|
|||
|
this.prepareReplacementString(
|
|||
|
replacement, portion, match, matchIndex
|
|||
|
)
|
|||
|
)
|
|||
|
if (!replacement.data) {
|
|||
|
return replacement
|
|||
|
}
|
|||
|
|
|||
|
if (!el) {
|
|||
|
return replacement
|
|||
|
}
|
|||
|
|
|||
|
el.appendChild(replacement)
|
|||
|
return el
|
|||
|
},
|
|||
|
|
|||
|
replaceMatch: function(match, startPortion, innerPortions, endPortion) {
|
|||
|
|
|||
|
var matchStartNode = startPortion.node
|
|||
|
var matchEndNode = endPortion.node
|
|||
|
var preceedingTextNode
|
|||
|
var followingTextNode
|
|||
|
if (matchStartNode === matchEndNode) {
|
|||
|
|
|||
|
var node = matchStartNode
|
|||
|
if (startPortion.indexInNode > 0) {
|
|||
|
// Add `before` text node (before the match)
|
|||
|
preceedingTextNode = doc.createTextNode(node.data.substring(0, startPortion.indexInNode))
|
|||
|
node.parentNode.insertBefore(preceedingTextNode, node)
|
|||
|
}
|
|||
|
|
|||
|
// Create the replacement node:
|
|||
|
var newNode = this.getPortionReplacementNode(
|
|||
|
endPortion,
|
|||
|
match
|
|||
|
)
|
|||
|
node.parentNode.insertBefore(newNode, node)
|
|||
|
if (endPortion.endIndexInNode < node.length) { // ?????
|
|||
|
// Add `after` text node (after the match)
|
|||
|
followingTextNode = doc.createTextNode(node.data.substring(endPortion.endIndexInNode))
|
|||
|
node.parentNode.insertBefore(followingTextNode, node)
|
|||
|
}
|
|||
|
|
|||
|
node.parentNode.removeChild(node)
|
|||
|
this.reverts.push(function() {
|
|||
|
if (preceedingTextNode === newNode.previousSibling) {
|
|||
|
preceedingTextNode.parentNode.removeChild(preceedingTextNode)
|
|||
|
}
|
|||
|
if (followingTextNode === newNode.nextSibling) {
|
|||
|
followingTextNode.parentNode.removeChild(followingTextNode)
|
|||
|
}
|
|||
|
newNode.parentNode.replaceChild(node, newNode)
|
|||
|
})
|
|||
|
return newNode
|
|||
|
} else {
|
|||
|
// Replace matchStartNode -> [innerMatchNodes...] -> matchEndNode (in that order)
|
|||
|
|
|||
|
preceedingTextNode = doc.createTextNode(
|
|||
|
matchStartNode.data.substring(0, startPortion.indexInNode)
|
|||
|
)
|
|||
|
followingTextNode = doc.createTextNode(
|
|||
|
matchEndNode.data.substring(endPortion.endIndexInNode)
|
|||
|
)
|
|||
|
var firstNode = this.getPortionReplacementNode(
|
|||
|
startPortion,
|
|||
|
match
|
|||
|
)
|
|||
|
var innerNodes = []
|
|||
|
for (var i = 0, l = innerPortions.length; i < l; ++i) {
|
|||
|
var portion = innerPortions[i]
|
|||
|
var innerNode = this.getPortionReplacementNode(
|
|||
|
portion,
|
|||
|
match
|
|||
|
)
|
|||
|
portion.node.parentNode.replaceChild(innerNode, portion.node)
|
|||
|
this.reverts.push((function(portion, innerNode) {
|
|||
|
return function() {
|
|||
|
innerNode.parentNode.replaceChild(portion.node, innerNode)
|
|||
|
}
|
|||
|
}(portion, innerNode)))
|
|||
|
innerNodes.push(innerNode)
|
|||
|
}
|
|||
|
|
|||
|
var lastNode = this.getPortionReplacementNode(
|
|||
|
endPortion,
|
|||
|
match
|
|||
|
)
|
|||
|
matchStartNode.parentNode.insertBefore(preceedingTextNode, matchStartNode)
|
|||
|
matchStartNode.parentNode.insertBefore(firstNode, matchStartNode)
|
|||
|
matchStartNode.parentNode.removeChild(matchStartNode)
|
|||
|
matchEndNode.parentNode.insertBefore(lastNode, matchEndNode)
|
|||
|
matchEndNode.parentNode.insertBefore(followingTextNode, matchEndNode)
|
|||
|
matchEndNode.parentNode.removeChild(matchEndNode)
|
|||
|
this.reverts.push(function() {
|
|||
|
preceedingTextNode.parentNode.removeChild(preceedingTextNode)
|
|||
|
firstNode.parentNode.replaceChild(matchStartNode, firstNode)
|
|||
|
followingTextNode.parentNode.removeChild(followingTextNode)
|
|||
|
lastNode.parentNode.replaceChild(matchEndNode, lastNode)
|
|||
|
})
|
|||
|
return lastNode
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
}
|
|||
|
return exposed
|
|||
|
}())
|
|||
|
|
|||
|
);
|
|||
|
|
|||
|
var isNodeNormalizeNormal = (function() {
|
|||
|
//// Disabled `Node.normalize()` for temp due to
|
|||
|
//// issue below in IE11.
|
|||
|
//// See: http://stackoverflow.com/questions/22337498/why-does-ie11-handle-node-normalize-incorrectly-for-the-minus-symbol
|
|||
|
var div = $.create( 'div' )
|
|||
|
|
|||
|
div.appendChild($.create( '', '0-' ))
|
|||
|
div.appendChild($.create( '', '2' ))
|
|||
|
div.normalize()
|
|||
|
|
|||
|
return div.firstChild.length !== 2
|
|||
|
})()
|
|||
|
|
|||
|
function getFuncOrElmt( obj ) {
|
|||
|
return (
|
|||
|
typeof obj === 'function' ||
|
|||
|
obj instanceof Element
|
|||
|
)
|
|||
|
? obj
|
|||
|
: undefined
|
|||
|
}
|
|||
|
|
|||
|
function createBDGroup( portion ) {
|
|||
|
var clazz = portion.index === 0 && portion.isEnd
|
|||
|
? 'biaodian cjk'
|
|||
|
: 'biaodian cjk portion ' + (
|
|||
|
portion.index === 0
|
|||
|
? 'is-first'
|
|||
|
: portion.isEnd
|
|||
|
? 'is-end'
|
|||
|
: 'is-inner'
|
|||
|
)
|
|||
|
|
|||
|
var $elmt = $.create( 'h-char-group', clazz )
|
|||
|
$elmt.innerHTML = portion.text
|
|||
|
return $elmt
|
|||
|
}
|
|||
|
|
|||
|
function createBDChar( char ) {
|
|||
|
var div = $.create( 'div' )
|
|||
|
var unicode = char.charCodeAt( 0 ).toString( 16 )
|
|||
|
|
|||
|
div.innerHTML = (
|
|||
|
'<h-char unicode="' + unicode +
|
|||
|
'" class="biaodian cjk ' + getBDType( char ) +
|
|||
|
'">' + char + '</h-char>'
|
|||
|
)
|
|||
|
return div.firstChild
|
|||
|
}
|
|||
|
|
|||
|
function getBDType( char ) {
|
|||
|
return char.match( TYPESET.char.biaodian.open )
|
|||
|
? 'bd-open'
|
|||
|
: char.match( TYPESET.char.biaodian.close )
|
|||
|
? 'bd-close bd-end'
|
|||
|
: char.match( TYPESET.char.biaodian.end )
|
|||
|
? (
|
|||
|
/(?:\u3001|\u3002|\uff0c)/i.test( char )
|
|||
|
? 'bd-end bd-cop'
|
|||
|
: 'bd-end'
|
|||
|
)
|
|||
|
: char.match(new RegExp( UNICODE.biaodian.liga ))
|
|||
|
? 'bd-liga'
|
|||
|
: char.match(new RegExp( UNICODE.biaodian.middle ))
|
|||
|
? 'bd-middle'
|
|||
|
: ''
|
|||
|
}
|
|||
|
|
|||
|
$.extend( Fibre.fn, {
|
|||
|
normalize: function() {
|
|||
|
if ( isNodeNormalizeNormal ) {
|
|||
|
this.context.normalize()
|
|||
|
}
|
|||
|
return this
|
|||
|
},
|
|||
|
|
|||
|
// Force punctuation & biaodian typesetting rules to be applied.
|
|||
|
jinzify: function( selector ) {
|
|||
|
return (
|
|||
|
this
|
|||
|
.filter( selector || null )
|
|||
|
.avoid( 'h-jinze' )
|
|||
|
.replace(
|
|||
|
TYPESET.jinze.touwei,
|
|||
|
function( portion, match ) {
|
|||
|
var elem = $.create( 'h-jinze', 'touwei' )
|
|||
|
elem.innerHTML = match[0]
|
|||
|
return (( portion.index === 0 && portion.isEnd ) || portion.index === 1 ) ? elem : ''
|
|||
|
}
|
|||
|
)
|
|||
|
.replace(
|
|||
|
TYPESET.jinze.wei,
|
|||
|
function( portion, match ) {
|
|||
|
var elem = $.create( 'h-jinze', 'wei' )
|
|||
|
elem.innerHTML = match[0]
|
|||
|
return portion.index === 0 ? elem : ''
|
|||
|
}
|
|||
|
)
|
|||
|
.replace(
|
|||
|
TYPESET.jinze.tou,
|
|||
|
function( portion, match ) {
|
|||
|
var elem = $.create( 'h-jinze', 'tou' )
|
|||
|
elem.innerHTML = match[0]
|
|||
|
return (( portion.index === 0 && portion.isEnd ) || portion.index === 1 )
|
|||
|
? elem : ''
|
|||
|
}
|
|||
|
)
|
|||
|
.replace(
|
|||
|
TYPESET.jinze.middle,
|
|||
|
function( portion, match ) {
|
|||
|
var elem = $.create( 'h-jinze', 'middle' )
|
|||
|
elem.innerHTML = match[0]
|
|||
|
return (( portion.index === 0 && portion.isEnd ) || portion.index === 1 )
|
|||
|
? elem : ''
|
|||
|
}
|
|||
|
)
|
|||
|
.endAvoid()
|
|||
|
.endFilter()
|
|||
|
)
|
|||
|
},
|
|||
|
|
|||
|
groupify: function( option ) {
|
|||
|
var option = $.extend({
|
|||
|
biaodian: false,
|
|||
|
//punct: false,
|
|||
|
hanzi: false, // Includes Kana
|
|||
|
kana: false,
|
|||
|
eonmun: false,
|
|||
|
western: false // Includes Latin, Greek and Cyrillic
|
|||
|
}, option || {})
|
|||
|
|
|||
|
this.avoid( 'h-word, h-char-group' )
|
|||
|
|
|||
|
if ( option.biaodian ) {
|
|||
|
this.replace(
|
|||
|
TYPESET.group.biaodian[0], createBDGroup
|
|||
|
).replace(
|
|||
|
TYPESET.group.biaodian[1], createBDGroup
|
|||
|
)
|
|||
|
}
|
|||
|
|
|||
|
if ( option.hanzi || option.cjk ) {
|
|||
|
this.wrap(
|
|||
|
TYPESET.group.hanzi, $.clone($.create( 'h-char-group', 'hanzi cjk' ))
|
|||
|
)
|
|||
|
}
|
|||
|
if ( option.western ) {
|
|||
|
this.wrap(
|
|||
|
TYPESET.group.western, $.clone($.create( 'h-word', 'western' ))
|
|||
|
)
|
|||
|
}
|
|||
|
if ( option.kana ) {
|
|||
|
this.wrap(
|
|||
|
TYPESET.group.kana, $.clone($.create( 'h-char-group', 'kana' ))
|
|||
|
)
|
|||
|
}
|
|||
|
if ( option.eonmun || option.hangul ) {
|
|||
|
this.wrap(
|
|||
|
TYPESET.group.eonmun, $.clone($.create( 'h-word', 'eonmun hangul' ))
|
|||
|
)
|
|||
|
}
|
|||
|
|
|||
|
this.endAvoid()
|
|||
|
return this
|
|||
|
},
|
|||
|
|
|||
|
charify: function( option ) {
|
|||
|
var option = $.extend({
|
|||
|
avoid: true,
|
|||
|
biaodian: false,
|
|||
|
punct: false,
|
|||
|
hanzi: false, // Includes Kana
|
|||
|
latin: false,
|
|||
|
ellinika: false,
|
|||
|
kirillica: false,
|
|||
|
kana: false,
|
|||
|
eonmun: false
|
|||
|
}, option || {})
|
|||
|
|
|||
|
if ( option.avoid ) {
|
|||
|
this.avoid( 'h-char' )
|
|||
|
}
|
|||
|
|
|||
|
if ( option.biaodian ) {
|
|||
|
this.replace(
|
|||
|
TYPESET.char.biaodian.all,
|
|||
|
getFuncOrElmt( option.biaodian )
|
|||
|
||
|
|||
|
function( portion ) { return createBDChar( portion.text ) }
|
|||
|
).replace(
|
|||
|
TYPESET.char.biaodian.liga,
|
|||
|
getFuncOrElmt( option.biaodian )
|
|||
|
||
|
|||
|
function( portion ) { return createBDChar( portion.text ) }
|
|||
|
)
|
|||
|
}
|
|||
|
if ( option.hanzi || option.cjk ) {
|
|||
|
this.wrap(
|
|||
|
TYPESET.char.hanzi,
|
|||
|
getFuncOrElmt( option.hanzi || option.cjk )
|
|||
|
||
|
|||
|
$.clone($.create( 'h-char', 'hanzi cjk' ))
|
|||
|
)
|
|||
|
}
|
|||
|
if ( option.punct ) {
|
|||
|
this.wrap(
|
|||
|
TYPESET.char.punct.all,
|
|||
|
getFuncOrElmt( option.punct )
|
|||
|
||
|
|||
|
$.clone($.create( 'h-char', 'punct' ))
|
|||
|
)
|
|||
|
}
|
|||
|
if ( option.latin ) {
|
|||
|
this.wrap(
|
|||
|
TYPESET.char.latin,
|
|||
|
getFuncOrElmt( option.latin )
|
|||
|
||
|
|||
|
$.clone($.create( 'h-char', 'alphabet latin' ))
|
|||
|
)
|
|||
|
}
|
|||
|
if ( option.ellinika || option.greek ) {
|
|||
|
this.wrap(
|
|||
|
TYPESET.char.ellinika,
|
|||
|
getFuncOrElmt( option.ellinika || option.greek )
|
|||
|
||
|
|||
|
$.clone($.create( 'h-char', 'alphabet ellinika greek' ))
|
|||
|
)
|
|||
|
}
|
|||
|
if ( option.kirillica || option.cyrillic ) {
|
|||
|
this.wrap(
|
|||
|
TYPESET.char.kirillica,
|
|||
|
getFuncOrElmt( option.kirillica || option.cyrillic )
|
|||
|
||
|
|||
|
$.clone($.create( 'h-char', 'alphabet kirillica cyrillic' ))
|
|||
|
)
|
|||
|
}
|
|||
|
if ( option.kana ) {
|
|||
|
this.wrap(
|
|||
|
TYPESET.char.kana,
|
|||
|
getFuncOrElmt( option.kana )
|
|||
|
||
|
|||
|
$.clone($.create( 'h-char', 'kana' ))
|
|||
|
)
|
|||
|
}
|
|||
|
if ( option.eonmun || option.hangul ) {
|
|||
|
this.wrap(
|
|||
|
TYPESET.char.eonmun,
|
|||
|
getFuncOrElmt( option.eonmun || option.hangul )
|
|||
|
||
|
|||
|
$.clone($.create( 'h-char', 'eonmun hangul' ))
|
|||
|
)
|
|||
|
}
|
|||
|
|
|||
|
this.endAvoid()
|
|||
|
return this
|
|||
|
}
|
|||
|
})
|
|||
|
|
|||
|
$.extend( Han, {
|
|||
|
isNodeNormalizeNormal: isNodeNormalizeNormal,
|
|||
|
find: Fibre,
|
|||
|
createBDGroup: createBDGroup,
|
|||
|
createBDChar: createBDChar
|
|||
|
})
|
|||
|
|
|||
|
$.matches = Han.find.matches
|
|||
|
|
|||
|
void [
|
|||
|
'setMode',
|
|||
|
'wrap', 'replace', 'revert',
|
|||
|
'addBoundary', 'removeBoundary',
|
|||
|
'avoid', 'endAvoid',
|
|||
|
'filter', 'endFilter',
|
|||
|
'jinzify', 'groupify', 'charify'
|
|||
|
].forEach(function( method ) {
|
|||
|
Han.fn[ method ] = function() {
|
|||
|
if ( !this.finder ) {
|
|||
|
// Share the same selector
|
|||
|
this.finder = Han.find( this.context )
|
|||
|
}
|
|||
|
|
|||
|
this.finder[ method ]( arguments[ 0 ], arguments[ 1 ] )
|
|||
|
return this
|
|||
|
}
|
|||
|
})
|
|||
|
|
|||
|
var Locale = {}
|
|||
|
|
|||
|
function writeOnCanvas( text, font ) {
|
|||
|
var canvas = $.create( 'canvas' )
|
|||
|
var context
|
|||
|
|
|||
|
canvas.width = '50'
|
|||
|
canvas.height = '20'
|
|||
|
canvas.style.display = 'none'
|
|||
|
|
|||
|
body.appendChild( canvas )
|
|||
|
|
|||
|
context = canvas.getContext( '2d' )
|
|||
|
context.textBaseline = 'top'
|
|||
|
context.font = '15px ' + font + ', sans-serif'
|
|||
|
context.fillStyle = 'black'
|
|||
|
context.strokeStyle = 'black'
|
|||
|
context.fillText( text, 0, 0 )
|
|||
|
|
|||
|
return {
|
|||
|
node: canvas,
|
|||
|
context: context,
|
|||
|
remove: function() {
|
|||
|
$.remove( canvas, body )
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function compareCanvases( treat, control ) {
|
|||
|
var ret
|
|||
|
var a = treat.context
|
|||
|
var b = control.context
|
|||
|
|
|||
|
try {
|
|||
|
for ( var j = 1; j <= 20; j++ ) {
|
|||
|
for ( var i = 1; i <= 50; i++ ) {
|
|||
|
if (
|
|||
|
typeof ret === 'undefined' &&
|
|||
|
a.getImageData(i, j, 1, 1).data[3] !== b.getImageData(i, j, 1, 1).data[3]
|
|||
|
) {
|
|||
|
ret = false
|
|||
|
break
|
|||
|
} else if ( typeof ret === 'boolean' ) {
|
|||
|
break
|
|||
|
}
|
|||
|
|
|||
|
if ( i === 50 && j === 20 && typeof ret === 'undefined' ) {
|
|||
|
ret = true
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
// Remove and clean from memory
|
|||
|
treat.remove()
|
|||
|
control.remove()
|
|||
|
treat = null
|
|||
|
control = null
|
|||
|
|
|||
|
return ret
|
|||
|
} catch (e) {}
|
|||
|
return false
|
|||
|
}
|
|||
|
|
|||
|
function detectFont( treat, control, text ) {
|
|||
|
var treat = treat
|
|||
|
var control = control || 'sans-serif'
|
|||
|
var text = text || '辭Q'
|
|||
|
var ret
|
|||
|
|
|||
|
control = writeOnCanvas( text, control )
|
|||
|
treat = writeOnCanvas( text, treat )
|
|||
|
|
|||
|
return !compareCanvases( treat, control )
|
|||
|
}
|
|||
|
|
|||
|
Locale.writeOnCanvas = writeOnCanvas
|
|||
|
Locale.compareCanvases = compareCanvases
|
|||
|
Locale.detectFont = detectFont
|
|||
|
|
|||
|
Locale.support = (function() {
|
|||
|
|
|||
|
var PREFIX = 'Webkit Moz ms'.split(' ')
|
|||
|
|
|||
|
// Create an element for feature detecting
|
|||
|
// (in `testCSSProp`)
|
|||
|
var elem = $.create( 'h-test' )
|
|||
|
|
|||
|
function testCSSProp( prop ) {
|
|||
|
var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1)
|
|||
|
var allProp = ( prop + ' ' + PREFIX.join( ucProp + ' ' ) + ucProp ).split(' ')
|
|||
|
var ret
|
|||
|
|
|||
|
allProp.forEach(function( prop ) {
|
|||
|
if ( typeof elem.style[ prop ] === 'string' ) {
|
|||
|
ret = true
|
|||
|
}
|
|||
|
})
|
|||
|
return ret || false
|
|||
|
}
|
|||
|
|
|||
|
function injectElementWithStyle( rule, callback ) {
|
|||
|
var fakeBody = body || $.create( 'body' )
|
|||
|
var div = $.create( 'div' )
|
|||
|
var container = body ? div : fakeBody
|
|||
|
var callback = typeof callback === 'function' ? callback : function() {}
|
|||
|
var style, ret, docOverflow
|
|||
|
|
|||
|
style = [ '<style>', rule, '</style>' ].join('')
|
|||
|
|
|||
|
container.innerHTML += style
|
|||
|
fakeBody.appendChild( div )
|
|||
|
|
|||
|
if ( !body ) {
|
|||
|
fakeBody.style.background = ''
|
|||
|
fakeBody.style.overflow = 'hidden'
|
|||
|
docOverflow = root.style.overflow
|
|||
|
|
|||
|
root.style.overflow = 'hidden'
|
|||
|
root.appendChild( fakeBody )
|
|||
|
}
|
|||
|
|
|||
|
// Callback
|
|||
|
ret = callback( container, rule )
|
|||
|
|
|||
|
// Remove the injected scope
|
|||
|
$.remove( container )
|
|||
|
if ( !body ) {
|
|||
|
root.style.overflow = docOverflow
|
|||
|
}
|
|||
|
return !!ret
|
|||
|
}
|
|||
|
|
|||
|
function getStyle( elem, prop ) {
|
|||
|
var ret
|
|||
|
|
|||
|
if ( window.getComputedStyle ) {
|
|||
|
ret = document.defaultView.getComputedStyle( elem, null ).getPropertyValue( prop )
|
|||
|
} else if ( elem.currentStyle ) {
|
|||
|
// for IE
|
|||
|
ret = elem.currentStyle[ prop ]
|
|||
|
}
|
|||
|
return ret
|
|||
|
}
|
|||
|
|
|||
|
return {
|
|||
|
columnwidth: testCSSProp( 'columnWidth' ),
|
|||
|
|
|||
|
fontface: (function() {
|
|||
|
var ret
|
|||
|
|
|||
|
injectElementWithStyle(
|
|||
|
'@font-face { font-family: font; src: url("//"); }',
|
|||
|
function( node, rule ) {
|
|||
|
var style = $.qsa( 'style', node )[0]
|
|||
|
var sheet = style.sheet || style.styleSheet
|
|||
|
var cssText = sheet ?
|
|||
|
( sheet.cssRules && sheet.cssRules[0] ?
|
|||
|
sheet.cssRules[0].cssText : sheet.cssText || ''
|
|||
|
) : ''
|
|||
|
|
|||
|
ret = /src/i.test( cssText ) &&
|
|||
|
cssText.indexOf( rule.split(' ')[0] ) === 0
|
|||
|
}
|
|||
|
)
|
|||
|
|
|||
|
return ret
|
|||
|
})(),
|
|||
|
|
|||
|
ruby: (function() {
|
|||
|
var ruby = $.create( 'ruby' )
|
|||
|
var rt = $.create( 'rt' )
|
|||
|
var rp = $.create( 'rp' )
|
|||
|
var ret
|
|||
|
|
|||
|
ruby.appendChild( rp )
|
|||
|
ruby.appendChild( rt )
|
|||
|
root.appendChild( ruby )
|
|||
|
|
|||
|
// Browsers that support ruby hide the `<rp>` via `display: none`
|
|||
|
ret = (
|
|||
|
getStyle( rp, 'display' ) === 'none' ||
|
|||
|
// but in IE, `<rp>` has `display: inline`, so the test needs other conditions:
|
|||
|
getStyle( ruby, 'display' ) === 'ruby' &&
|
|||
|
getStyle( rt, 'display' ) === 'ruby-text'
|
|||
|
) ? true : false
|
|||
|
|
|||
|
// Remove and clean from memory
|
|||
|
root.removeChild( ruby )
|
|||
|
ruby = null
|
|||
|
rt = null
|
|||
|
rp = null
|
|||
|
|
|||
|
return ret
|
|||
|
})(),
|
|||
|
|
|||
|
'ruby-display': (function() {
|
|||
|
var div = $.create( 'div' )
|
|||
|
|
|||
|
div.innerHTML = '<h-test-a style="display: ruby;"></h-test-a><h-test-b style="display: ruby-text-container;"></h-test-b>'
|
|||
|
return div.querySelector( 'h-test-a' ).style.display === 'ruby' && div.querySelector( 'h-test-b' ).style.display === 'ruby-text-container'
|
|||
|
})(),
|
|||
|
|
|||
|
'ruby-interchar': (function() {
|
|||
|
var IC = 'inter-character'
|
|||
|
var div = $.create( 'div' )
|
|||
|
var css
|
|||
|
|
|||
|
div.innerHTML = '<h-test style="-moz-ruby-position:' + IC + ';-ms-ruby-position:' + IC + ';-webkit-ruby-position:' + IC + ';ruby-position:' + IC + ';"></h-test>'
|
|||
|
css = div.querySelector( 'h-test' ).style
|
|||
|
return css.rubyPosition === IC || css.WebkitRubyPosition === IC || css.MozRubyPosition === IC || css.msRubyPosition === IC
|
|||
|
})(),
|
|||
|
|
|||
|
textemphasis: testCSSProp( 'textEmphasis' ),
|
|||
|
|
|||
|
// Address feature support test for `unicode-range` via
|
|||
|
// detecting whether it's Arial (supported) or
|
|||
|
// Times New Roman (not supported).
|
|||
|
unicoderange: (function() {
|
|||
|
var ret
|
|||
|
|
|||
|
injectElementWithStyle(
|
|||
|
'@font-face{font-family:test-for-unicode-range;src:local(Arial),local("Droid Sans")}@font-face{font-family:test-for-unicode-range;src:local("Times New Roman"),local(Times),local("Droid Serif");unicode-range:U+270C}',
|
|||
|
function() {
|
|||
|
ret = !Locale.detectFont(
|
|||
|
'test-for-unicode-range', // treatment group
|
|||
|
'Arial, "Droid Sans"', // control group
|
|||
|
'Q' // ASCII characters only
|
|||
|
)
|
|||
|
}
|
|||
|
)
|
|||
|
return ret
|
|||
|
})(),
|
|||
|
|
|||
|
writingmode: testCSSProp( 'writingMode' )
|
|||
|
}
|
|||
|
})()
|
|||
|
|
|||
|
Locale.initCond = function( target ) {
|
|||
|
var target = target || root
|
|||
|
var ret = ''
|
|||
|
var clazz
|
|||
|
|
|||
|
for ( var feature in Locale.support ) {
|
|||
|
clazz = ( Locale.support[ feature ] ? '' : 'no-' ) + feature
|
|||
|
|
|||
|
target.classList.add( clazz )
|
|||
|
ret += clazz + ' '
|
|||
|
}
|
|||
|
return ret
|
|||
|
}
|
|||
|
|
|||
|
var SUPPORT_IC = Locale.support[ 'ruby-interchar' ]
|
|||
|
|
|||
|
// 1. Simple ruby polyfill;
|
|||
|
// 2. Inter-character polyfill for Zhuyin
|
|||
|
function renderSimpleRuby( $ruby ) {
|
|||
|
var frag = $.create( '!' )
|
|||
|
var clazz = $ruby.classList
|
|||
|
var $rb, $ru
|
|||
|
|
|||
|
frag.appendChild( $.clone( $ruby ))
|
|||
|
|
|||
|
$
|
|||
|
.tag( 'rt', frag.firstChild )
|
|||
|
.forEach(function( $rt ) {
|
|||
|
var $rb = $.create( '!' )
|
|||
|
var airb = []
|
|||
|
var irb
|
|||
|
|
|||
|
// Consider the previous nodes the implied
|
|||
|
// ruby base
|
|||
|
do {
|
|||
|
irb = ( irb || $rt ).previousSibling
|
|||
|
if ( !irb || irb.nodeName.match( /((?:h\-)?r[ubt])/i )) break
|
|||
|
|
|||
|
$rb.insertBefore( $.clone( irb ), $rb.firstChild )
|
|||
|
airb.push( irb )
|
|||
|
} while ( !irb.nodeName.match( /((?:h\-)?r[ubt])/i ))
|
|||
|
|
|||
|
// Create a real `<h-ru>` to append.
|
|||
|
$ru = clazz.contains( 'zhuyin' ) ? createZhuyinRu( $rb, $rt ) : createNormalRu( $rb, $rt )
|
|||
|
|
|||
|
// Replace the ruby text with the new `<h-ru>`,
|
|||
|
// and remove the original implied ruby base(s)
|
|||
|
try {
|
|||
|
$rt.parentNode.replaceChild( $ru, $rt )
|
|||
|
airb.map( $.remove )
|
|||
|
} catch ( e ) {}
|
|||
|
})
|
|||
|
return createCustomRuby( frag )
|
|||
|
}
|
|||
|
|
|||
|
function renderInterCharRuby( $ruby ) {
|
|||
|
var frag = $.create( '!' )
|
|||
|
frag.appendChild( $.clone( $ruby ))
|
|||
|
|
|||
|
$
|
|||
|
.tag( 'rt', frag.firstChild )
|
|||
|
.forEach(function( $rt ) {
|
|||
|
var $rb = $.create( '!' )
|
|||
|
var airb = []
|
|||
|
var irb, $zhuyin
|
|||
|
|
|||
|
// Consider the previous nodes the implied
|
|||
|
// ruby base
|
|||
|
do {
|
|||
|
irb = ( irb || $rt ).previousSibling
|
|||
|
if ( !irb || irb.nodeName.match( /((?:h\-)?r[ubt])/i )) break
|
|||
|
|
|||
|
$rb.insertBefore( $.clone( irb ), $rb.firstChild )
|
|||
|
airb.push( irb )
|
|||
|
} while ( !irb.nodeName.match( /((?:h\-)?r[ubt])/i ))
|
|||
|
|
|||
|
$zhuyin = $.create( 'rt' )
|
|||
|
$zhuyin.innerHTML = getZhuyinHTML( $rt )
|
|||
|
$rt.parentNode.replaceChild( $zhuyin, $rt )
|
|||
|
})
|
|||
|
return frag.firstChild
|
|||
|
}
|
|||
|
|
|||
|
// 3. Complex ruby polyfill
|
|||
|
// - Double-lined annotation;
|
|||
|
// - Right-angled annotation.
|
|||
|
function renderComplexRuby( $ruby ) {
|
|||
|
var frag = $.create( '!' )
|
|||
|
var clazz = $ruby.classList
|
|||
|
var $cloned, $rb, $ru, maxspan
|
|||
|
|
|||
|
frag.appendChild( $.clone( $ruby ))
|
|||
|
$cloned = frag.firstChild
|
|||
|
|
|||
|
$rb = $ru = $.tag( 'rb', $cloned )
|
|||
|
maxspan = $rb.length
|
|||
|
|
|||
|
// First of all, deal with Zhuyin containers
|
|||
|
// individually
|
|||
|
//
|
|||
|
// Note that we only support one single Zhuyin
|
|||
|
// container in each complex ruby
|
|||
|
void function( $rtc ) {
|
|||
|
if ( !$rtc ) return
|
|||
|
|
|||
|
$ru = $
|
|||
|
.tag( 'rt', $rtc )
|
|||
|
.map(function( $rt, i ) {
|
|||
|
if ( !$rb[ i ] ) return
|
|||
|
var ret = createZhuyinRu( $rb[ i ], $rt )
|
|||
|
|
|||
|
try {
|
|||
|
$rb[ i ].parentNode.replaceChild( ret, $rb[ i ] )
|
|||
|
} catch ( e ) {}
|
|||
|
return ret
|
|||
|
})
|
|||
|
|
|||
|
// Remove the container once it's useless
|
|||
|
$.remove( $rtc )
|
|||
|
$cloned.setAttribute( 'rightangle', 'true' )
|
|||
|
}( $cloned.querySelector( 'rtc.zhuyin' ))
|
|||
|
|
|||
|
// Then, normal annotations other than Zhuyin
|
|||
|
$
|
|||
|
.qsa( 'rtc:not(.zhuyin)', $cloned )
|
|||
|
.forEach(function( $rtc, order ) {
|
|||
|
var ret
|
|||
|
ret = $
|
|||
|
.tag( 'rt', $rtc )
|
|||
|
.map(function( $rt, i ) {
|
|||
|
var rbspan = Number( $rt.getAttribute( 'rbspan' ) || 1 )
|
|||
|
var span = 0
|
|||
|
var aRb = []
|
|||
|
var $rb, ret
|
|||
|
|
|||
|
if ( rbspan > maxspan ) rbspan = maxspan
|
|||
|
|
|||
|
do {
|
|||
|
try {
|
|||
|
$rb = $ru.shift()
|
|||
|
aRb.push( $rb )
|
|||
|
} catch (e) {}
|
|||
|
|
|||
|
if ( typeof $rb === 'undefined' ) break
|
|||
|
span += Number( $rb.getAttribute( 'span' ) || 1 )
|
|||
|
} while ( rbspan > span )
|
|||
|
|
|||
|
if ( rbspan < span ) {
|
|||
|
if ( aRb.length > 1 ) {
|
|||
|
console.error( 'An impossible `rbspan` value detected.', ruby )
|
|||
|
return
|
|||
|
}
|
|||
|
aRb = $.tag( 'rb', aRb[0] )
|
|||
|
$ru = aRb.slice( rbspan ).concat( $ru )
|
|||
|
aRb = aRb.slice( 0, rbspan )
|
|||
|
span = rbspan
|
|||
|
}
|
|||
|
|
|||
|
ret = createNormalRu( aRb, $rt, {
|
|||
|
'class': clazz,
|
|||
|
span: span,
|
|||
|
order: order
|
|||
|
})
|
|||
|
|
|||
|
try {
|
|||
|
aRb[0].parentNode.replaceChild( ret, aRb.shift() )
|
|||
|
aRb.map( $.remove )
|
|||
|
} catch (e) {}
|
|||
|
return ret
|
|||
|
})
|
|||
|
$ru = ret
|
|||
|
if ( order === 1 ) $cloned.setAttribute( 'doubleline', 'true' )
|
|||
|
|
|||
|
// Remove the container once it's useless
|
|||
|
$.remove( $rtc )
|
|||
|
})
|
|||
|
return createCustomRuby( frag )
|
|||
|
}
|
|||
|
|
|||
|
// Create a new fake `<h-ruby>` element so the
|
|||
|
// style sheets will render it as a polyfill,
|
|||
|
// which also helps to avoid the UA style.
|
|||
|
function createCustomRuby( frag ) {
|
|||
|
var $ruby = frag.firstChild
|
|||
|
var hruby = $.create( 'h-ruby' )
|
|||
|
|
|||
|
hruby.innerHTML = $ruby.innerHTML
|
|||
|
$.setAttr( hruby, $ruby.attributes )
|
|||
|
hruby.normalize()
|
|||
|
return hruby
|
|||
|
}
|
|||
|
|
|||
|
function simplifyRubyClass( elem ) {
|
|||
|
if ( !elem instanceof Element ) return elem
|
|||
|
var clazz = elem.classList
|
|||
|
|
|||
|
if ( clazz.contains( 'pinyin' )) clazz.add( 'romanization' )
|
|||
|
else if ( clazz.contains( 'romanization' )) clazz.add( 'annotation' )
|
|||
|
else if ( clazz.contains( 'mps' )) clazz.add( 'zhuyin' )
|
|||
|
else if ( clazz.contains( 'rightangle' )) clazz.add( 'complex' )
|
|||
|
return elem
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Create and return a new `<h-ru>` element
|
|||
|
* according to the given contents
|
|||
|
*/
|
|||
|
function createNormalRu( $rb, $rt, attr ) {
|
|||
|
var $ru = $.create( 'h-ru' )
|
|||
|
var $rt = $.clone( $rt )
|
|||
|
var attr = attr || {}
|
|||
|
attr.annotation = 'true'
|
|||
|
|
|||
|
if ( Array.isArray( $rb )) {
|
|||
|
$ru.innerHTML = $rb.map(function( rb ) {
|
|||
|
if ( typeof rb === 'undefined' ) return ''
|
|||
|
return rb.outerHTML
|
|||
|
}).join('') + $rt.outerHTML
|
|||
|
} else {
|
|||
|
$ru.appendChild( $.clone( $rb ))
|
|||
|
$ru.appendChild( $rt )
|
|||
|
}
|
|||
|
|
|||
|
$.setAttr( $ru, attr )
|
|||
|
return $ru
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Create and return a new `<h-ru>` element
|
|||
|
* in Zhuyin form
|
|||
|
*/
|
|||
|
function createZhuyinRu( $rb, $rt ) {
|
|||
|
var $rb = $.clone( $rb )
|
|||
|
|
|||
|
// Create an element to return
|
|||
|
var $ru = $.create( 'h-ru' )
|
|||
|
$ru.setAttribute( 'zhuyin', true )
|
|||
|
|
|||
|
// - <h-ru zhuyin>
|
|||
|
// - <rb><rb/>
|
|||
|
// - <h-zhuyin>
|
|||
|
// - <h-yin></h-yin>
|
|||
|
// - <h-diao></h-diao>
|
|||
|
// - </h-zhuyin>
|
|||
|
// - </h-ru>
|
|||
|
$ru.appendChild( $rb )
|
|||
|
$ru.innerHTML += getZhuyinHTML( $rt )
|
|||
|
return $ru
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Create a Zhuyin-form HTML string
|
|||
|
*/
|
|||
|
function getZhuyinHTML( rt ) {
|
|||
|
// #### Explanation ####
|
|||
|
// * `zhuyin`: the entire phonetic annotation
|
|||
|
// * `yin`: the plain pronunciation (w/out tone)
|
|||
|
// * `diao`: the tone
|
|||
|
// * `len`: the length of the plain pronunciation (`yin`)
|
|||
|
var zhuyin = typeof rt === 'string' ? rt : rt.textContent
|
|||
|
var yin, diao, len
|
|||
|
|
|||
|
yin = zhuyin.replace( TYPESET.zhuyin.diao, '' )
|
|||
|
len = yin ? yin.length : 0
|
|||
|
diao = zhuyin
|
|||
|
.replace( yin, '' )
|
|||
|
.replace( /[\u02C5]/g, '\u02C7' )
|
|||
|
.replace( /[\u030D]/g, '\u0358' )
|
|||
|
return len === 0 ? '' : '<h-zhuyin length="' + len + '" diao="' + diao + '"><h-yin>' + yin + '</h-yin><h-diao>' + diao + '</h-diao></h-zhuyin>'
|
|||
|
}
|
|||
|
|
|||
|
/**
|
|||
|
* Normalize `ruby` elements
|
|||
|
*/
|
|||
|
$.extend( Locale, {
|
|||
|
|
|||
|
// Address normalisation for both simple and complex
|
|||
|
// rubies (interlinear annotations)
|
|||
|
renderRuby: function( context, target ) {
|
|||
|
var target = target || 'ruby'
|
|||
|
var $target = $.qsa( target, context )
|
|||
|
|
|||
|
$.qsa( 'rtc', context )
|
|||
|
.concat( $target ).map( simplifyRubyClass )
|
|||
|
|
|||
|
$target
|
|||
|
.forEach(function( $ruby ) {
|
|||
|
var clazz = $ruby.classList
|
|||
|
var $new
|
|||
|
|
|||
|
if ( clazz.contains( 'complex' )) $new = renderComplexRuby( $ruby )
|
|||
|
else if ( clazz.contains( 'zhuyin' )) $new = SUPPORT_IC ? renderInterCharRuby( $ruby ) : renderSimpleRuby( $ruby )
|
|||
|
|
|||
|
// Finally, replace it
|
|||
|
if ( $new ) $ruby.parentNode.replaceChild( $new, $ruby )
|
|||
|
})
|
|||
|
},
|
|||
|
|
|||
|
simplifyRubyClass: simplifyRubyClass,
|
|||
|
getZhuyinHTML: getZhuyinHTML,
|
|||
|
renderComplexRuby: renderComplexRuby,
|
|||
|
renderSimpleRuby: renderSimpleRuby,
|
|||
|
renderInterCharRuby: renderInterCharRuby
|
|||
|
|
|||
|
// ### TODO list ###
|
|||
|
//
|
|||
|
// * Debug mode
|
|||
|
// * Better error-tolerance
|
|||
|
})
|
|||
|
|
|||
|
/**
|
|||
|
* Normalisation rendering mechanism
|
|||
|
*/
|
|||
|
$.extend( Locale, {
|
|||
|
|
|||
|
// Render and normalise the given context by routine:
|
|||
|
//
|
|||
|
// ruby -> u, ins -> s, del -> em
|
|||
|
//
|
|||
|
renderElem: function( context ) {
|
|||
|
this.renderRuby( context )
|
|||
|
this.renderDecoLine( context )
|
|||
|
this.renderDecoLine( context, 's, del' )
|
|||
|
this.renderEm( context )
|
|||
|
},
|
|||
|
|
|||
|
// Traverse all target elements and address
|
|||
|
// presentational corrections if any two of
|
|||
|
// them are adjacent to each other.
|
|||
|
renderDecoLine: function( context, target ) {
|
|||
|
var $$target = $.qsa( target || 'u, ins', context )
|
|||
|
var i = $$target.length
|
|||
|
|
|||
|
traverse: while ( i-- ) {
|
|||
|
var $this = $$target[ i ]
|
|||
|
var $prev = null
|
|||
|
|
|||
|
// Ignore all `<wbr>` and comments in between,
|
|||
|
// and add class `.adjacent` once two targets
|
|||
|
// are next to each other.
|
|||
|
ignore: do {
|
|||
|
$prev = ( $prev || $this ).previousSibling
|
|||
|
|
|||
|
if ( !$prev ) {
|
|||
|
continue traverse
|
|||
|
} else if ( $$target[ i-1 ] === $prev ) {
|
|||
|
$this.classList.add( 'adjacent' )
|
|||
|
}
|
|||
|
} while ( $.isIgnorable( $prev ))
|
|||
|
}
|
|||
|
},
|
|||
|
|
|||
|
// Traverse all target elements to render
|
|||
|
// emphasis marks.
|
|||
|
renderEm: function( context, target ) {
|
|||
|
var method = target ? 'qsa' : 'tag'
|
|||
|
var target = target || 'em'
|
|||
|
var $target = $[ method ]( target, context )
|
|||
|
|
|||
|
$target
|
|||
|
.forEach(function( elem ) {
|
|||
|
var $elem = Han( elem )
|
|||
|
|
|||
|
if ( Locale.support.textemphasis ) {
|
|||
|
$elem
|
|||
|
.avoid( 'rt, h-char' )
|
|||
|
.charify({ biaodian: true, punct: true })
|
|||
|
} else {
|
|||
|
$elem
|
|||
|
.avoid( 'rt, h-char, h-char-group' )
|
|||
|
.jinzify()
|
|||
|
.groupify({ western: true })
|
|||
|
.charify({
|
|||
|
hanzi: true,
|
|||
|
biaodian: true,
|
|||
|
punct: true,
|
|||
|
latin: true,
|
|||
|
ellinika: true,
|
|||
|
kirillica: true
|
|||
|
})
|
|||
|
}
|
|||
|
})
|
|||
|
}
|
|||
|
})
|
|||
|
|
|||
|
Han.normalize = Locale
|
|||
|
Han.localize = Locale
|
|||
|
Han.support = Locale.support
|
|||
|
Han.detectFont = Locale.detectFont
|
|||
|
|
|||
|
Han.fn.initCond = function() {
|
|||
|
this.condition.classList.add( 'han-js-rendered' )
|
|||
|
Han.normalize.initCond( this.condition )
|
|||
|
return this
|
|||
|
}
|
|||
|
|
|||
|
void [
|
|||
|
'Elem',
|
|||
|
'DecoLine',
|
|||
|
'Em',
|
|||
|
'Ruby'
|
|||
|
].forEach(function( elem ) {
|
|||
|
var method = 'render' + elem
|
|||
|
|
|||
|
Han.fn[ method ] = function( target ) {
|
|||
|
Han.normalize[ method ]( this.context, target )
|
|||
|
return this
|
|||
|
}
|
|||
|
})
|
|||
|
|
|||
|
$.extend( Han.support, {
|
|||
|
// Assume that all devices support Heiti for we
|
|||
|
// use `sans-serif` to do the comparison.
|
|||
|
heiti: true,
|
|||
|
// 'heiti-gb': true,
|
|||
|
|
|||
|
songti: Han.detectFont( '"Han Songti"' ),
|
|||
|
'songti-gb': Han.detectFont( '"Han Songti GB"' ),
|
|||
|
|
|||
|
kaiti: Han.detectFont( '"Han Kaiti"' ),
|
|||
|
// 'kaiti-gb': Han.detectFont( '"Han Kaiti GB"' ),
|
|||
|
|
|||
|
fangsong: Han.detectFont( '"Han Fangsong"' )
|
|||
|
// 'fangsong-gb': Han.detectFont( '"Han Fangsong GB"' )
|
|||
|
})
|
|||
|
|
|||
|
Han.correctBiaodian = function( context ) {
|
|||
|
var context = context || document
|
|||
|
var finder = Han.find( context )
|
|||
|
|
|||
|
finder
|
|||
|
.avoid( 'h-char' )
|
|||
|
.replace( /([‘“])/g, function( portion ) {
|
|||
|
var $char = Han.createBDChar( portion.text )
|
|||
|
$char.classList.add( 'bd-open', 'punct' )
|
|||
|
return $char
|
|||
|
})
|
|||
|
.replace( /([’”])/g, function( portion ) {
|
|||
|
var $char = Han.createBDChar( portion.text )
|
|||
|
$char.classList.add( 'bd-close', 'bd-end', 'punct' )
|
|||
|
return $char
|
|||
|
})
|
|||
|
|
|||
|
return Han.support.unicoderange
|
|||
|
? finder
|
|||
|
: finder.charify({ biaodian: true })
|
|||
|
}
|
|||
|
|
|||
|
Han.correctBasicBD = Han.correctBiaodian
|
|||
|
Han.correctBD = Han.correctBiaodian
|
|||
|
|
|||
|
$.extend( Han.fn, {
|
|||
|
biaodian: null,
|
|||
|
|
|||
|
correctBiaodian: function() {
|
|||
|
this.biaodian = Han.correctBiaodian( this.context )
|
|||
|
return this
|
|||
|
},
|
|||
|
|
|||
|
revertCorrectedBiaodian: function() {
|
|||
|
try {
|
|||
|
this.biaodian.revert( 'all' )
|
|||
|
} catch (e) {}
|
|||
|
return this
|
|||
|
}
|
|||
|
})
|
|||
|
|
|||
|
// Legacy support (deprecated):
|
|||
|
Han.fn.correctBasicBD = Han.fn.correctBiaodian
|
|||
|
Han.fn.revertBasicBD = Han.fn.revertCorrectedBiaodian
|
|||
|
|
|||
|
var hws = '<<hws>>'
|
|||
|
|
|||
|
var $hws = $.create( 'h-hws' )
|
|||
|
$hws.setAttribute( 'hidden', '' )
|
|||
|
$hws.innerHTML = ' '
|
|||
|
|
|||
|
function sharingSameParent( $a, $b ) {
|
|||
|
return $a && $b && $a.parentNode === $b.parentNode
|
|||
|
}
|
|||
|
|
|||
|
function properlyPlaceHWSBehind( $node, text ) {
|
|||
|
var $elmt = $node
|
|||
|
var text = text || ''
|
|||
|
|
|||
|
if (
|
|||
|
$.isElmt( $node.nextSibling ) ||
|
|||
|
sharingSameParent( $node, $node.nextSibling )
|
|||
|
) {
|
|||
|
return text + hws
|
|||
|
} else {
|
|||
|
// One of the parental elements of the current text
|
|||
|
// node would definitely have a next sibling, since
|
|||
|
// it is of the first portion and not `isEnd`.
|
|||
|
while ( !$elmt.nextSibling ) {
|
|||
|
$elmt = $elmt.parentNode
|
|||
|
}
|
|||
|
if ( $node !== $elmt ) {
|
|||
|
$elmt.insertAdjacentHTML( 'afterEnd', '<h-hws hidden> </h-hws>' )
|
|||
|
}
|
|||
|
}
|
|||
|
return text
|
|||
|
}
|
|||
|
|
|||
|
function firstStepLabel( portion, mat ) {
|
|||
|
return portion.isEnd && portion.index === 0
|
|||
|
? mat[1] + hws + mat[2]
|
|||
|
: portion.index === 0
|
|||
|
? properlyPlaceHWSBehind( portion.node, portion.text )
|
|||
|
: portion.text
|
|||
|
}
|
|||
|
|
|||
|
function real$hwsElmt( portion ) {
|
|||
|
return portion.index === 0
|
|||
|
? $.clone( $hws )
|
|||
|
: ''
|
|||
|
}
|
|||
|
|
|||
|
var last$hwsIdx
|
|||
|
|
|||
|
function apostrophe( portion ) {
|
|||
|
var $elmt = portion.node.parentNode
|
|||
|
|
|||
|
if ( portion.index === 0 ) {
|
|||
|
last$hwsIdx = portion.endIndexInNode-2
|
|||
|
}
|
|||
|
|
|||
|
if (
|
|||
|
$elmt.nodeName.toLowerCase() === 'h-hws' && (
|
|||
|
portion.index === 1 || portion.indexInMatch === last$hwsIdx
|
|||
|
)) {
|
|||
|
$elmt.classList.add( 'quote-inner' )
|
|||
|
}
|
|||
|
return portion.text
|
|||
|
}
|
|||
|
|
|||
|
function curveQuote( portion ) {
|
|||
|
var $elmt = portion.node.parentNode
|
|||
|
|
|||
|
if ( $elmt.nodeName.toLowerCase() === 'h-hws' ) {
|
|||
|
$elmt.classList.add( 'quote-outer' )
|
|||
|
}
|
|||
|
return portion.text
|
|||
|
}
|
|||
|
|
|||
|
$.extend( Han, {
|
|||
|
renderHWS: function( context, strict ) {
|
|||
|
// Elements to be filtered according to the
|
|||
|
// HWS rendering mode.
|
|||
|
var AVOID = strict
|
|||
|
? 'textarea, code, kbd, samp, pre'
|
|||
|
: 'textarea'
|
|||
|
|
|||
|
var mode = strict ? 'strict' : 'base'
|
|||
|
var context = context || document
|
|||
|
var finder = Han.find( context )
|
|||
|
|
|||
|
finder
|
|||
|
.avoid( AVOID )
|
|||
|
|
|||
|
// Basic situations:
|
|||
|
// - 字a => 字<hws/>a
|
|||
|
// - A字 => A<hws/>字
|
|||
|
.replace( Han.TYPESET.hws[ mode ][0], firstStepLabel )
|
|||
|
.replace( Han.TYPESET.hws[ mode ][1], firstStepLabel )
|
|||
|
|
|||
|
// Convert text nodes `<hws/>` into real element nodes:
|
|||
|
.replace( new RegExp( '(' + hws + ')+', 'g' ), real$hwsElmt )
|
|||
|
|
|||
|
// Deal with:
|
|||
|
// - '<hws/>字<hws/>' => '字'
|
|||
|
// - "<hws/>字<hws/>" => "字"
|
|||
|
.replace( /([\'"])\s(.+?)\s\1/g, apostrophe )
|
|||
|
|
|||
|
// Deal with:
|
|||
|
// - <hws/>“字”<hws/>
|
|||
|
// - <hws/>‘字’<hws/>
|
|||
|
.replace( /\s[‘“]/g, curveQuote )
|
|||
|
.replace( /[’”]\s/g, curveQuote )
|
|||
|
.normalize()
|
|||
|
|
|||
|
// Return the finder instance for future usage
|
|||
|
return finder
|
|||
|
}
|
|||
|
})
|
|||
|
|
|||
|
$.extend( Han.fn, {
|
|||
|
renderHWS: function( strict ) {
|
|||
|
Han.renderHWS( this.context, strict )
|
|||
|
return this
|
|||
|
},
|
|||
|
|
|||
|
revertHWS: function() {
|
|||
|
$.tag( 'h-hws', this.context )
|
|||
|
.forEach(function( hws ) {
|
|||
|
$.remove( hws )
|
|||
|
})
|
|||
|
this.HWS = []
|
|||
|
return this
|
|||
|
}
|
|||
|
})
|
|||
|
|
|||
|
var HANGABLE_CLASS = 'bd-hangable'
|
|||
|
var HANGABLE_AVOID = 'h-char.bd-hangable'
|
|||
|
var HANGABLE_CS_HTML = '<h-cs hidden class="jinze-outer hangable-outer"> </h-cs>'
|
|||
|
|
|||
|
var matches = Han.find.matches
|
|||
|
|
|||
|
function detectSpaceFont() {
|
|||
|
var div = $.create( 'div' )
|
|||
|
var ret
|
|||
|
|
|||
|
div.innerHTML = '<span>a b</span><span style="font-family: \'Han Space\'">a b</span>'
|
|||
|
body.appendChild( div )
|
|||
|
ret = div.firstChild.offsetWidth !== div.lastChild.offsetWidth
|
|||
|
$.remove( div )
|
|||
|
return ret
|
|||
|
}
|
|||
|
|
|||
|
function insertHangableCS( $jinze ) {
|
|||
|
var $cs = $jinze.nextSibling
|
|||
|
|
|||
|
if ( $cs && matches( $cs, 'h-cs.jinze-outer' )) {
|
|||
|
$cs.classList.add( 'hangable-outer' )
|
|||
|
} else {
|
|||
|
$jinze.insertAdjacentHTML(
|
|||
|
'afterend',
|
|||
|
HANGABLE_CS_HTML
|
|||
|
)
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
Han.support['han-space'] = detectSpaceFont()
|
|||
|
|
|||
|
$.extend( Han, {
|
|||
|
detectSpaceFont: detectSpaceFont,
|
|||
|
isSpaceFontLoaded: detectSpaceFont(),
|
|||
|
|
|||
|
renderHanging: function( context ) {
|
|||
|
var context = context || document
|
|||
|
var finder = Han.find( context )
|
|||
|
|
|||
|
finder
|
|||
|
.avoid( 'textarea, code, kbd, samp, pre' )
|
|||
|
.avoid( HANGABLE_AVOID )
|
|||
|
.replace(
|
|||
|
TYPESET.jinze.hanging,
|
|||
|
function( portion ) {
|
|||
|
if ( /^[\x20\t\r\n\f]+$/.test( portion.text )) {
|
|||
|
return ''
|
|||
|
}
|
|||
|
|
|||
|
var $elmt = portion.node.parentNode
|
|||
|
var $jinze, $new, $bd, biaodian
|
|||
|
|
|||
|
if ( $jinze = $.parent( $elmt, 'h-jinze' )) {
|
|||
|
insertHangableCS( $jinze )
|
|||
|
}
|
|||
|
|
|||
|
biaodian = portion.text.trim()
|
|||
|
|
|||
|
$new = Han.createBDChar( biaodian )
|
|||
|
$new.innerHTML = '<h-inner>' + biaodian + '</h-inner>'
|
|||
|
$new.classList.add( HANGABLE_CLASS )
|
|||
|
|
|||
|
$bd = $.parent( $elmt, 'h-char.biaodian' )
|
|||
|
|
|||
|
return !$bd
|
|||
|
? $new
|
|||
|
: (function() {
|
|||
|
$bd.classList.add( HANGABLE_CLASS )
|
|||
|
|
|||
|
return matches( $elmt, 'h-inner, h-inner *' )
|
|||
|
? biaodian
|
|||
|
: $new.firstChild
|
|||
|
})()
|
|||
|
}
|
|||
|
)
|
|||
|
return finder
|
|||
|
}
|
|||
|
})
|
|||
|
|
|||
|
$.extend( Han.fn, {
|
|||
|
renderHanging: function() {
|
|||
|
var classList = this.condition.classList
|
|||
|
Han.isSpaceFontLoaded = detectSpaceFont()
|
|||
|
|
|||
|
if (
|
|||
|
Han.isSpaceFontLoaded &&
|
|||
|
classList.contains( 'no-han-space' )
|
|||
|
) {
|
|||
|
classList.remove( 'no-han-space' )
|
|||
|
classList.add( 'han-space' )
|
|||
|
}
|
|||
|
|
|||
|
Han.renderHanging( this.context )
|
|||
|
return this
|
|||
|
},
|
|||
|
|
|||
|
revertHanging: function() {
|
|||
|
$.qsa(
|
|||
|
'h-char.bd-hangable, h-cs.hangable-outer',
|
|||
|
this.context
|
|||
|
).forEach(function( $elmt ) {
|
|||
|
var classList = $elmt.classList
|
|||
|
classList.remove( 'bd-hangable' )
|
|||
|
classList.remove( 'hangable-outer' )
|
|||
|
})
|
|||
|
return this
|
|||
|
}
|
|||
|
})
|
|||
|
|
|||
|
var JIYA_CLASS = 'bd-jiya'
|
|||
|
var JIYA_AVOID = 'h-char.bd-jiya'
|
|||
|
var CONSECUTIVE_CLASS = 'bd-consecutive'
|
|||
|
var JIYA_CS_HTML = '<h-cs hidden class="jinze-outer jiya-outer"> </h-cs>'
|
|||
|
|
|||
|
var matches = Han.find.matches
|
|||
|
|
|||
|
function trimBDClass( clazz ) {
|
|||
|
return clazz.replace(
|
|||
|
/(biaodian|cjk|bd-jiya|bd-consecutive|bd-hangable)/gi, ''
|
|||
|
).trim()
|
|||
|
}
|
|||
|
|
|||
|
function charifyBiaodian( portion ) {
|
|||
|
var biaodian = portion.text
|
|||
|
var $elmt = portion.node.parentNode
|
|||
|
var $bd = $.parent( $elmt, 'h-char.biaodian' )
|
|||
|
var $new = Han.createBDChar( biaodian )
|
|||
|
var $jinze
|
|||
|
|
|||
|
$new.innerHTML = '<h-inner>' + biaodian + '</h-inner>'
|
|||
|
$new.classList.add( JIYA_CLASS )
|
|||
|
|
|||
|
if ( $jinze = $.parent( $elmt, 'h-jinze' )) {
|
|||
|
insertJiyaCS( $jinze )
|
|||
|
}
|
|||
|
|
|||
|
return !$bd
|
|||
|
? $new
|
|||
|
: (function() {
|
|||
|
$bd.classList.add( JIYA_CLASS )
|
|||
|
|
|||
|
return matches( $elmt, 'h-inner, h-inner *' )
|
|||
|
? biaodian
|
|||
|
: $new.firstChild
|
|||
|
})()
|
|||
|
}
|
|||
|
|
|||
|
var prevBDType, $$prevCS
|
|||
|
|
|||
|
function locateConsecutiveBD( portion ) {
|
|||
|
var prev = prevBDType
|
|||
|
var $elmt = portion.node.parentNode
|
|||
|
var $bd = $.parent( $elmt, 'h-char.biaodian' )
|
|||
|
var $jinze = $.parent( $bd, 'h-jinze' )
|
|||
|
var classList
|
|||
|
|
|||
|
classList = $bd.classList
|
|||
|
|
|||
|
if ( prev ) {
|
|||
|
$bd.setAttribute( 'prev', prev )
|
|||
|
}
|
|||
|
|
|||
|
if ( $$prevCS && classList.contains( 'bd-open' )) {
|
|||
|
$$prevCS.pop().setAttribute( 'next', 'bd-open' )
|
|||
|
}
|
|||
|
|
|||
|
$$prevCS = undefined
|
|||
|
|
|||
|
if ( portion.isEnd ) {
|
|||
|
prevBDType = undefined
|
|||
|
classList.add( CONSECUTIVE_CLASS, 'end-portion' )
|
|||
|
} else {
|
|||
|
prevBDType = trimBDClass($bd.getAttribute( 'class' ))
|
|||
|
classList.add( CONSECUTIVE_CLASS )
|
|||
|
}
|
|||
|
|
|||
|
if ( $jinze ) {
|
|||
|
$$prevCS = locateCS( $jinze, {
|
|||
|
prev: prev,
|
|||
|
'class': trimBDClass($bd.getAttribute( 'class' ))
|
|||
|
})
|
|||
|
}
|
|||
|
return portion.text
|
|||
|
}
|
|||
|
|
|||
|
function insertJiyaCS( $jinze ) {
|
|||
|
if (
|
|||
|
matches( $jinze, '.tou, .touwei' ) &&
|
|||
|
!matches( $jinze.previousSibling, 'h-cs.jiya-outer' )
|
|||
|
) {
|
|||
|
$jinze.insertAdjacentHTML( 'beforebegin', JIYA_CS_HTML )
|
|||
|
}
|
|||
|
if (
|
|||
|
matches( $jinze, '.wei, .touwei' ) &&
|
|||
|
!matches( $jinze.nextSibling, 'h-cs.jiya-outer' )
|
|||
|
) {
|
|||
|
$jinze.insertAdjacentHTML( 'afterend', JIYA_CS_HTML )
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function locateCS( $jinze, attr ) {
|
|||
|
var $prev, $next
|
|||
|
|
|||
|
if (matches( $jinze, '.tou, .touwei' )) {
|
|||
|
$prev = $jinze.previousSibling
|
|||
|
|
|||
|
if (matches( $prev, 'h-cs' )) {
|
|||
|
$prev.className = 'jinze-outer jiya-outer'
|
|||
|
$prev.setAttribute( 'prev', attr.prev )
|
|||
|
}
|
|||
|
}
|
|||
|
if (matches( $jinze, '.wei, .touwei' )) {
|
|||
|
$next = $jinze.nextSibling
|
|||
|
|
|||
|
if (matches( $next, 'h-cs' )) {
|
|||
|
$next.className = 'jinze-outer jiya-outer ' + attr[ 'class' ]
|
|||
|
$next.removeAttribute( 'prev' )
|
|||
|
}
|
|||
|
}
|
|||
|
return [ $prev, $next ]
|
|||
|
}
|
|||
|
|
|||
|
Han.renderJiya = function( context ) {
|
|||
|
var context = context || document
|
|||
|
var finder = Han.find( context )
|
|||
|
|
|||
|
finder
|
|||
|
.avoid( 'textarea, code, kbd, samp, pre, h-cs' )
|
|||
|
|
|||
|
.avoid( JIYA_AVOID )
|
|||
|
.charify({
|
|||
|
avoid: false,
|
|||
|
biaodian: charifyBiaodian
|
|||
|
})
|
|||
|
// End avoiding `JIYA_AVOID`:
|
|||
|
.endAvoid()
|
|||
|
|
|||
|
.avoid( 'textarea, code, kbd, samp, pre, h-cs' )
|
|||
|
.replace( TYPESET.group.biaodian[0], locateConsecutiveBD )
|
|||
|
.replace( TYPESET.group.biaodian[1], locateConsecutiveBD )
|
|||
|
|
|||
|
return finder
|
|||
|
}
|
|||
|
|
|||
|
$.extend( Han.fn, {
|
|||
|
renderJiya: function() {
|
|||
|
Han.renderJiya( this.context )
|
|||
|
return this
|
|||
|
},
|
|||
|
|
|||
|
revertJiya: function() {
|
|||
|
$.qsa(
|
|||
|
'h-char.bd-jiya, h-cs.jiya-outer',
|
|||
|
this.context
|
|||
|
).forEach(function( $elmt ) {
|
|||
|
var classList = $elmt.classList
|
|||
|
classList.remove( 'bd-jiya' )
|
|||
|
classList.remove( 'jiya-outer' )
|
|||
|
})
|
|||
|
return this
|
|||
|
}
|
|||
|
})
|
|||
|
|
|||
|
var QUERY_RU_W_ANNO = 'h-ru[annotation]'
|
|||
|
var SELECTOR_TO_IGNORE = 'textarea, code, kbd, samp, pre'
|
|||
|
|
|||
|
function createCompareFactory( font, treat, control ) {
|
|||
|
return function() {
|
|||
|
var a = Han.localize.writeOnCanvas( treat, font )
|
|||
|
var b = Han.localize.writeOnCanvas( control, font )
|
|||
|
return Han.localize.compareCanvases( a, b )
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
function isVowelCombLigaNormal() {
|
|||
|
return createCompareFactory( '"Romanization Sans"', '\u0061\u030D', '\uDB80\uDC61' )
|
|||
|
}
|
|||
|
|
|||
|
function isVowelICombLigaNormal() {
|
|||
|
return createCompareFactory( '"Romanization Sans"', '\u0069\u030D', '\uDB80\uDC69' )
|
|||
|
}
|
|||
|
|
|||
|
function isZhuyinCombLigaNormal() {
|
|||
|
return createCompareFactory( '"Zhuyin Kaiti"', '\u31B4\u0358', '\uDB8C\uDDB4' )
|
|||
|
}
|
|||
|
|
|||
|
function createSubstFactory( regexToSubst ) {
|
|||
|
return function( context ) {
|
|||
|
var context = context || document
|
|||
|
var finder = Han.find( context ).avoid( SELECTOR_TO_IGNORE )
|
|||
|
|
|||
|
regexToSubst
|
|||
|
.forEach(function( pattern ) {
|
|||
|
finder
|
|||
|
.replace(
|
|||
|
new RegExp( pattern[ 0 ], 'ig' ),
|
|||
|
function( portion, match ) {
|
|||
|
var ret = $.clone( charCombLiga )
|
|||
|
|
|||
|
// Put the original content in an inner container
|
|||
|
// for better presentational effect of hidden text
|
|||
|
ret.innerHTML = '<h-inner>' + match[0] + '</h-inner>'
|
|||
|
ret.setAttribute( 'display-as', pattern[ 1 ] )
|
|||
|
return portion.index === 0 ? ret : ''
|
|||
|
}
|
|||
|
)
|
|||
|
})
|
|||
|
return finder
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
var charCombLiga = $.create( 'h-char', 'comb-liga' )
|
|||
|
|
|||
|
$.extend( Han, {
|
|||
|
isVowelCombLigaNormal: isVowelCombLigaNormal(),
|
|||
|
isVowelICombLigaNormal: isVowelICombLigaNormal(),
|
|||
|
isZhuyinCombLigaNormal: isZhuyinCombLigaNormal(),
|
|||
|
|
|||
|
isCombLigaNormal: isVowelICombLigaNormal()(), // ### Deprecated
|
|||
|
|
|||
|
substVowelCombLiga: createSubstFactory( Han.TYPESET[ 'display-as' ][ 'comb-liga-vowel' ] ),
|
|||
|
substZhuyinCombLiga: createSubstFactory( Han.TYPESET[ 'display-as' ][ 'comb-liga-zhuyin' ] ),
|
|||
|
substCombLigaWithPUA: createSubstFactory( Han.TYPESET[ 'display-as' ][ 'comb-liga-pua' ] ),
|
|||
|
|
|||
|
substInaccurateChar: function( context ) {
|
|||
|
var context = context || document
|
|||
|
var finder = Han.find( context )
|
|||
|
|
|||
|
finder.avoid( SELECTOR_TO_IGNORE )
|
|||
|
|
|||
|
Han.TYPESET[ 'inaccurate-char' ]
|
|||
|
.forEach(function( pattern ) {
|
|||
|
finder
|
|||
|
.replace(
|
|||
|
new RegExp( pattern[ 0 ], 'ig' ),
|
|||
|
pattern[ 1 ]
|
|||
|
)
|
|||
|
})
|
|||
|
}
|
|||
|
})
|
|||
|
|
|||
|
$.extend( Han.fn, {
|
|||
|
'comb-liga-vowel': null,
|
|||
|
'comb-liga-vowel-i': null,
|
|||
|
'comb-liga-zhuyin': null,
|
|||
|
'inaccurate-char': null,
|
|||
|
|
|||
|
substVowelCombLiga: function() {
|
|||
|
this['comb-liga-vowel'] = Han.substVowelCombLiga( this.context )
|
|||
|
return this
|
|||
|
},
|
|||
|
|
|||
|
substVowelICombLiga: function() {
|
|||
|
this['comb-liga-vowel-i'] = Han.substVowelICombLiga( this.context )
|
|||
|
return this
|
|||
|
},
|
|||
|
|
|||
|
substZhuyinCombLiga: function() {
|
|||
|
this['comb-liga-zhuyin'] = Han.substZhuyinCombLiga( this.context )
|
|||
|
return this
|
|||
|
},
|
|||
|
|
|||
|
substCombLigaWithPUA: function() {
|
|||
|
if ( !Han.isVowelCombLigaNormal()) {
|
|||
|
this['comb-liga-vowel'] = Han.substVowelCombLiga( this.context )
|
|||
|
} else if ( !Han.isVowelICombLigaNormal()) {
|
|||
|
this['comb-liga-vowel-i'] = Han.substVowelICombLiga( this.context )
|
|||
|
}
|
|||
|
|
|||
|
if ( !Han.isZhuyinCombLigaNormal()) {
|
|||
|
this['comb-liga-zhuyin'] = Han.substZhuyinCombLiga( this.context )
|
|||
|
}
|
|||
|
return this
|
|||
|
},
|
|||
|
|
|||
|
revertVowelCombLiga: function() {
|
|||
|
try {
|
|||
|
this['comb-liga-vowel'].revert( 'all' )
|
|||
|
} catch (e) {}
|
|||
|
return this
|
|||
|
},
|
|||
|
|
|||
|
revertVowelICombLiga: function() {
|
|||
|
try {
|
|||
|
this['comb-liga-vowel-i'].revert( 'all' )
|
|||
|
} catch (e) {}
|
|||
|
return this
|
|||
|
},
|
|||
|
|
|||
|
revertZhuyinCombLiga: function() {
|
|||
|
try {
|
|||
|
this['comb-liga-zhuyin'].revert( 'all' )
|
|||
|
} catch (e) {}
|
|||
|
return this
|
|||
|
},
|
|||
|
|
|||
|
revertCombLigaWithPUA: function() {
|
|||
|
try {
|
|||
|
this['comb-liga-vowel'].revert( 'all' )
|
|||
|
this['comb-liga-vowel-i'].revert( 'all' )
|
|||
|
this['comb-liga-zhuyin'].revert( 'all' )
|
|||
|
} catch (e) {}
|
|||
|
return this
|
|||
|
},
|
|||
|
|
|||
|
substInaccurateChar: function() {
|
|||
|
this['inaccurate-char'] = Han.substInaccurateChar( this.context )
|
|||
|
return this
|
|||
|
},
|
|||
|
|
|||
|
revertInaccurateChar: function() {
|
|||
|
try {
|
|||
|
this['inaccurate-char'].revert( 'all' )
|
|||
|
} catch (e) {}
|
|||
|
return this
|
|||
|
}
|
|||
|
})
|
|||
|
|
|||
|
window.addEventListener( 'DOMContentLoaded', function() {
|
|||
|
var initContext
|
|||
|
|
|||
|
// Use the shortcut under the default situation
|
|||
|
if ( root.classList.contains( 'han-init' )) {
|
|||
|
Han.init()
|
|||
|
|
|||
|
// Consider ‘a configured context’ the special
|
|||
|
// case of the default situation. Will have to
|
|||
|
// replace the `Han.init` with the instance as
|
|||
|
// well (for future usage).
|
|||
|
} else if ( initContext = document.querySelector( '.han-init-context' )) {
|
|||
|
Han.init = Han( initContext ).render()
|
|||
|
}
|
|||
|
})
|
|||
|
|
|||
|
// Expose to global namespace
|
|||
|
if ( typeof noGlobalNS === 'undefined' || noGlobalNS === false ) {
|
|||
|
window.Han = Han
|
|||
|
}
|
|||
|
|
|||
|
return Han
|
|||
|
});
|
|||
|
|