hanzi-rails 0.0.2 → 0.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2375 @@
1
+ /*!
2
+ * 漢字標準格式 v3.1.0 | MIT License | css.hanzi.co
3
+ * Han.css: the CSS typography framework optimised for Hanzi
4
+ */
5
+
6
+ void function( global, factory ) {
7
+
8
+ // CommonJS
9
+ if ( typeof module === 'object' && typeof module.exports === 'object' ) {
10
+ module.exports = factory( global, true )
11
+ } else {
12
+ factory( global )
13
+ }
14
+
15
+ }( typeof window !== 'undefined' ? window : this, function( window, noGlobalNS ) {
16
+
17
+ 'use strict'
18
+
19
+ var document = window.document
20
+
21
+ var root = document.documentElement
22
+
23
+ var body = document.body
24
+
25
+ var VERSION = '3.1.0'
26
+
27
+ var ROUTINE = [
28
+ // Initialise the condition with feature-detecting
29
+ // classes (Modernizr-alike), binding onto the root
30
+ // element, possibly `<html>`.
31
+ 'initCond',
32
+ // Address element normalisation
33
+ 'renderElem',
34
+ // Handle Biaodian
35
+ //'jinzify',
36
+ 'renderJiya',
37
+ // Address Hanzi and Western script mixed spacing
38
+ 'renderHWS',
39
+ // Address Basic Biaodian correction in Firefox
40
+ 'correctBasicBD',
41
+ // Address presentational correction to combining ligatures
42
+ 'substCombLigaWithPUA'
43
+ // Address semantic correction to inaccurate characters
44
+ // **Note:** inactivated by default
45
+ // 'substInaccurateChar'
46
+ ]
47
+
48
+ // Define Han
49
+ var Han = function( context, condition ) {
50
+ return new Han.fn.init( context, condition )
51
+ }
52
+
53
+ var init = function() {
54
+ if ( arguments[ 0 ] ) {
55
+ this.context = arguments[ 0 ]
56
+ }
57
+ if ( arguments[ 1 ] ) {
58
+ this.condition = arguments[ 1 ]
59
+ }
60
+ return this
61
+ }
62
+
63
+ Han.version = VERSION
64
+
65
+ Han.fn = Han.prototype = {
66
+ version: VERSION,
67
+
68
+ constructor: Han,
69
+
70
+ // Body as the default target context
71
+ context: body,
72
+
73
+ // Root element as the default condition
74
+ condition: root,
75
+
76
+ // Default rendering routine
77
+ routine: ROUTINE,
78
+
79
+ init: init,
80
+
81
+ setRoutine: function( routine ) {
82
+ if ( Array.isArray( routine )) {
83
+ this.routine = routine
84
+ }
85
+ return this
86
+ },
87
+
88
+ // Note that the routine set up here will execute
89
+ // only once. The method won't alter the routine in
90
+ // the instance or in the prototype chain.
91
+ render: function( routine ) {
92
+ var it = this
93
+ var routine = Array.isArray( routine ) ? routine : this.routine
94
+
95
+ routine
96
+ .forEach(function( method ) {
97
+ try {
98
+ if ( typeof method === 'string' ) {
99
+ it[ method ]()
100
+ } else if ( Array.isArray( method )) {
101
+ it[ method.shift() ].apply( it, method )
102
+ }
103
+ } catch ( e ) {}
104
+ })
105
+ return this
106
+ }
107
+ }
108
+
109
+ Han.fn.init.prototype = Han.fn
110
+
111
+ /**
112
+ * Shortcut for `render()` under the default
113
+ * situation.
114
+ *
115
+ * Once initialised, replace `Han.init` with the
116
+ * instance for future usage.
117
+ */
118
+ Han.init = function() {
119
+ return Han.init = Han().render()
120
+ }
121
+
122
+ var UNICODE = {
123
+ /**
124
+ * Western punctuation (西文標點符號)
125
+ */
126
+ punct: {
127
+ base: '[\u2026,.;:!?\u203D_]',
128
+ sing: '[\u2010-\u2014\u2026]',
129
+ middle: '[\\\/~\\-&\u2010-\u2014_]',
130
+ open: '[\'"‘“\\(\\[\u00A1\u00BF\u2E18\u00AB\u2039\u201A\u201C\u201E]',
131
+ close: '[\'"”’\\)\\]\u00BB\u203A\u201B\u201D\u201F]',
132
+ end: '[\'"”’\\)\\]\u00BB\u203A\u201B\u201D\u201F\u203C\u203D\u2047-\u2049,.;:!?]',
133
+ },
134
+
135
+ /**
136
+ * CJK biaodian (CJK標點符號)
137
+ */
138
+ biaodian: {
139
+ base: '[︰.、,。:;?!ー]',
140
+ liga: '[—…⋯]',
141
+ middle: '[·\/-゠\uFF06\u30FB\uFF3F]',
142
+ open: '[「『《〈(〔[{【〖]',
143
+ close: '[」』》〉)〕]}】〗]',
144
+ end: '[」』》〉)〕]}】〗︰.、,。:;?!ー]'
145
+ },
146
+
147
+ /**
148
+ * CJK-related blocks (CJK相關字符區段)
149
+ *
150
+ * 1. 中日韓統一表意文字:[\u4E00-\u9FFF]
151
+ Basic CJK unified ideographs
152
+ * 2. 擴展-A區:[\u3400-\u4DB5]
153
+ Extended-A
154
+ * 3. 擴展-B區:[\u20000-\u2A6D6]([\uD840-\uD869][\uDC00-\uDED6])
155
+ Extended-B
156
+ * 4. 擴展-C區:[\u2A700-\u2B734](\uD86D[\uDC00-\uDF3F]|[\uD86A-\uD86C][\uDC00-\uDFFF]|\uD869[\uDF00-\uDFFF])
157
+ Extended-C
158
+ * 5. 擴展-D區:[\u2B740-\u2B81D](急用漢字,\uD86D[\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1F])
159
+ Extended-D
160
+ * 6. 擴展-E區:[\u2B820-\u2F7FF](暫未支援)
161
+ Extended-E (not supported yet)
162
+ * 7. 擴展-F區(暫未支援)
163
+ Extended-F (not supported yet)
164
+ * 8. 筆畫區:[\u31C0-\u31E3]
165
+ Strokes
166
+ * 9. 表意數字「〇」:[\u3007]
167
+ Ideographic number zero
168
+ * 10. 相容表意文字及補充:[\uF900-\uFAFF][\u2F800-\u2FA1D](不使用)
169
+ Compatibility ideograph and supplement (not supported)
170
+
171
+ 12 exceptions:
172
+ [\uFA0E\uFA0F\uFA11\uFA13\uFA14\uFA1F\uFA21\uFA23\uFA24\uFA27-\uFA29]
173
+
174
+ https://zh.wikipedia.org/wiki/中日韓統一表意文字#cite_note-1
175
+
176
+ * 11. 康熙字典及簡化字部首:[\u2F00-\u2FD5\u2E80-\u2EF3]
177
+ Kangxi and supplement radicals
178
+ * 12. 表意文字描述字元:[\u2FF0-\u2FFA]
179
+ Ideographic description characters
180
+ */
181
+ hanzi: {
182
+ base: '[\u4E00-\u9FFF\u3400-\u4DB5\u31C0-\u31E3\u3007\uFA0E\uFA0F\uFA11\uFA13\uFA14\uFA1F\uFA21\uFA23\uFA24\uFA27-\uFA29]|[\uD800-\uDBFF][\uDC00-\uDFFF]',
183
+ desc: '[\u2FF0-\u2FFA]',
184
+ radical: '[\u2F00-\u2FD5\u2E80-\u2EF3]'
185
+ },
186
+
187
+ /**
188
+ * Latin script blocks (拉丁字母區段)
189
+ *
190
+ * 1. 基本拉丁字母:A-Za-z
191
+ Basic Latin
192
+ * 2. 阿拉伯數字:0-9
193
+ Digits
194
+ * 3. 補充-1:[\u00C0-\u00FF]
195
+ Latin-1 supplement
196
+ * 4. 擴展-A區:[\u0100-\u017F]
197
+ Extended-A
198
+ * 5. 擴展-B區:[\u0180-\u024F]
199
+ Extended-B
200
+ * 5. 擴展-C區:[\u2C60-\u2C7F]
201
+ Extended-C
202
+ * 5. 擴展-D區:[\uA720-\uA7FF]
203
+ Extended-D
204
+ * 6. 附加區:[\u1E00-\u1EFF]
205
+ Extended additional
206
+ * 7. 變音組字符:[\u0300-\u0341\u1DC0-\u1DFF]
207
+ Combining diacritical marks
208
+ */
209
+ latin: {
210
+ base: '[A-Za-z0-9\u00C0-\u00FF\u0100-\u017F\u0180-\u024F\u2C60-\u2C7F\uA720-\uA7FF\u1E00-\u1EFF]',
211
+ combine: '[\u0300-\u0341\u1DC0-\u1DFF]'
212
+ },
213
+
214
+ /**
215
+ * Elli̱niká (Greek) script blocks (希臘字母區段)
216
+ *
217
+ * 1. 希臘字母及擴展:[\u0370–\u03FF\u1F00-\u1FFF]
218
+ Basic Greek & Greek Extended
219
+ * 2. 阿拉伯數字:0-9
220
+ Digits
221
+ * 3. 希臘字母變音組字符:[\u0300-\u0345\u1DC0-\u1DFF]
222
+ Combining diacritical marks
223
+ */
224
+ ellinika: {
225
+ base: '[0-9\u0370-\u03FF\u1F00-\u1FFF]',
226
+ combine: '[\u0300-\u0345\u1DC0-\u1DFF]'
227
+ },
228
+
229
+ /**
230
+ * Kirillica (Cyrillic) script blocks (西里爾字母區段)
231
+ *
232
+ * 1. 西里爾字母及補充:[\u0400-\u0482\u048A-\u04FF\u0500-\u052F]
233
+ Basic Cyrillic and supplement
234
+ * 2. 擴展B區:[\uA640-\uA66E\uA67E-\uA697]
235
+ Extended-B
236
+ * 3. 阿拉伯數字:0-9
237
+ Digits
238
+ * 4. 西里爾字母組字符:[\u0483-\u0489\u2DE0-\u2DFF\uA66F-\uA67D\uA69F](位擴展A、B區)
239
+ Cyrillic combining diacritical marks (in extended-A, B)
240
+ */
241
+ kirillica: {
242
+ base: '[0-9\u0400-\u0482\u048A-\u04FF\u0500-\u052F\uA640-\uA66E\uA67E-\uA697]',
243
+ combine: '[\u0483-\u0489\u2DE0-\u2DFF\uA66F-\uA67D\uA69F]'
244
+ },
245
+
246
+ /**
247
+ * Kana (假名)
248
+ *
249
+ * 1. 日文假名:[\u30A2\u30A4\u30A6\u30A8\u30AA-\u30FA\u3042\u3044\u3046\u3048\u304A-\u3094\u309F\u30FF]
250
+ Japanese Kana
251
+ * 2. 假名補充[\u1B000\u1B001](\uD82C[\uDC00-\uDC01])
252
+ Kana supplement
253
+ * 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]
254
+ Japanese small Kana
255
+ * 4. 假名組字符:[\u3099-\u309C]
256
+ Kana combining characters
257
+ * 5. 半形假名:[\uFF66-\uFF9F]
258
+ Halfwidth Kana
259
+ * 6. 符號:[\u309D\u309E\u30FB-\u30FE]
260
+ Marks
261
+ */
262
+ kana: {
263
+ base: '[\u30A2\u30A4\u30A6\u30A8\u30AA-\u30FA\u3042\u3044\u3046\u3048\u304A-\u3094\u309F\u30FF]|\uD82C[\uDC00-\uDC01]',
264
+ 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]',
265
+ combine: '[\u3099-\u309C]',
266
+ half: '[\uFF66-\uFF9F]',
267
+ mark: '[\u30A0\u309D\u309E\u30FB-\u30FE]'
268
+ },
269
+
270
+ /**
271
+ * Eonmun (Hangul, 諺文)
272
+ *
273
+ * 1. 諺文音節:[\uAC00-\uD7A3]
274
+ Eonmun (Hangul) syllables
275
+ * 2. 諺文字母:[\u1100-\u11FF\u314F-\u3163\u3131-\u318E\uA960-\uA97C\uD7B0-\uD7FB]
276
+ Eonmun (Hangul) letters
277
+ * 3. 半形諺文字母:[\uFFA1-\uFFDC]
278
+ Halfwidth Eonmun (Hangul) letters
279
+ */
280
+ eonmun: {
281
+ base: '[\uAC00-\uD7A3]',
282
+ letter: '[\u1100-\u11FF\u314F-\u3163\u3131-\u318E\uA960-\uA97C\uD7B0-\uD7FB]',
283
+ half: '[\uFFA1-\uFFDC]'
284
+ },
285
+
286
+ /**
287
+ * Zhuyin (注音符號, Mandarin & Dialect Phonetic Symbols)
288
+ *
289
+ * 1. 國語注音、方言音符號:[\u3105-\u312D][\u31A0-\u31BA]
290
+ Bopomofo phonetic symbols
291
+ * 2. 國語陰陽上去聲調號:[\u02D9\u02CA\u02C5\u02C7\u02CB] (**註:**三聲包含乙個不合規範的符號)
292
+ Tones for Mandarin
293
+ * 3. 方言音陰、陽去聲調號:[\u02EA\u02EB]
294
+ Departing tones in dialects
295
+ * 4. 方言音陰、陽入韻:[\u31B4-\u31B7][\u0358\u030d]?
296
+ Checked tones in dialects
297
+ */
298
+ zhuyin: {
299
+ base: '[\u3105-\u312D\u31A0-\u31BA]',
300
+ initial: '[\u3105-\u3119\u312A-\u312C\u31A0-\u31A3]',
301
+ medial: '[\u3127-\u3129]',
302
+ final: '[\u311A-\u3129\u312D\u31A4-\u31B3\u31B8-\u31BA]',
303
+ tone: '[\u02D9\u02CA\u02C5\u02C7\u02CB\u02EA\u02EB]',
304
+ ruyun: '[\u31B4-\u31B7][\u0358\u030d]?'
305
+ }
306
+ }
307
+
308
+ var TYPESET = (function() {
309
+ var rWhite = '[\\x20\\t\\r\\n\\f]'
310
+ // Whitespace characters
311
+ // http://www.w3.org/TR/css3-selectors/#whitespace
312
+
313
+ var rPtOpen = UNICODE.punct.open
314
+ var rPtClose = UNICODE.punct.close
315
+ var rPtEnd = UNICODE.punct.end
316
+ var rPtMid = UNICODE.punct.middle
317
+ var rPtSing = UNICODE.punct.sing
318
+ var rPt = rPtOpen + '|' + rPtEnd + '|' + rPtMid
319
+
320
+ var rBdOpen = UNICODE.biaodian.open
321
+ var rBdClose = UNICODE.biaodian.close
322
+ var rBdEnd = UNICODE.biaodian.end
323
+ var rBdMid = UNICODE.biaodian.middle
324
+ var rBdLiga = UNICODE.biaodian.liga + '{2}'
325
+ var rBd = rBdOpen + '|' + rBdEnd + '|' + rBdMid
326
+
327
+ var rKana = UNICODE.kana.base + UNICODE.kana.combine + '?'
328
+ var rKanaS = UNICODE.kana.small + UNICODE.kana.combine + '?'
329
+ var rKanaH = UNICODE.kana.half
330
+ var rEon = UNICODE.eonmun.base + '|' + UNICODE.eonmun.letter
331
+ var rEonH = UNICODE.eonmun.half
332
+
333
+ var rHan = UNICODE.hanzi.base + '|' + UNICODE.hanzi.desc + '|' + UNICODE.hanzi.radical + '|' + rKana
334
+
335
+ var rCbn = UNICODE.ellinika.combine
336
+ var rLatn = UNICODE.latin.base + rCbn + '*'
337
+ var rGk = UNICODE.ellinika.base + rCbn + '*'
338
+
339
+ var rCyCbn = UNICODE.kirillica.combine
340
+ var rCy = UNICODE.kirillica.base + rCyCbn + '*'
341
+
342
+ var rAlph = rLatn + '|' + rGk + '|' + rCy
343
+
344
+ // For words like `it's`, `Jones’s` or `'99`
345
+ var rApo = '[\u0027\u2019]'
346
+ var rChar = rHan + '|(' + rAlph + '|' + rApo + ')+'
347
+
348
+ var rZyS = UNICODE.zhuyin.initial
349
+ var rZyJ = UNICODE.zhuyin.medial
350
+ var rZyY = UNICODE.zhuyin.final
351
+ var rZyD = UNICODE.zhuyin.tone + '|' + UNICODE.zhuyin.ruyun
352
+
353
+ return {
354
+ /* Character-level selector (字級選擇器)
355
+ */
356
+ char: {
357
+ punct: {
358
+ all: new RegExp( '(' + rPt + ')', 'g' ),
359
+ open: new RegExp( '(' + rPtOpen + ')', 'g' ),
360
+ end: new RegExp( '(' + rPtEnd + ')', 'g' ),
361
+ sing: new RegExp( '(' + rPtSing + ')', 'g' )
362
+ },
363
+
364
+ biaodian: {
365
+ all: new RegExp( '(' + rBd + ')', 'g' ),
366
+ open: new RegExp( '(' + rBdOpen + ')', 'g' ),
367
+ close: new RegExp( '(' + rBdClose + ')', 'g' ),
368
+ end: new RegExp( '(' + rBdEnd + ')', 'g' ),
369
+ liga: new RegExp( '(' + rBdLiga + ')', 'g' ),
370
+
371
+ group: [
372
+ new RegExp( '(' + rBdOpen + '|' + rBdMid + '|' + rBdEnd + '){2,}', 'g' ),
373
+ new RegExp( '(' + rBdLiga + rBdOpen + ')', 'g' )
374
+ ]
375
+ },
376
+
377
+ hanzi: {
378
+ individual: new RegExp( '(' + rHan + ')', 'g' ),
379
+ group: new RegExp( '(' + rHan + ')+', 'g' )
380
+ },
381
+
382
+ word: new RegExp( '(' + rLatn + '|' + rGk + '|' + rCy + '|' + rPt + ')+', 'ig' ),
383
+
384
+ alphabet: {
385
+ latin: new RegExp( '(' + rLatn + ')', 'ig' ),
386
+ ellinika: new RegExp( '(' + rGk + ')', 'ig' ),
387
+ kirillica: new RegExp( '(' + rCy + ')', 'ig' ),
388
+ kana: new RegExp( '(' + rKana + ')', 'g' ),
389
+ smallkana: new RegExp( '(' + rKanaS + ')', 'g' ),
390
+ eonmun: new RegExp( '(' + rEon + ')', 'g' ),
391
+ halfeonmun: new RegExp( '(' + rEonH + ')', 'g' )
392
+ }
393
+ },
394
+
395
+ /* Punctuation Rules (禁則)
396
+ */
397
+ jinze: {
398
+ touwei: new RegExp( '(' + rBdOpen + '+)(' + rChar + ')(' + rBdEnd + '+)', 'ig' ),
399
+ tou: new RegExp( '(' + rBdOpen + '+)(' + rChar + ')', 'ig' ),
400
+ wei: new RegExp( '(' + rChar + ')(' + rBdEnd + '+)', 'ig' ),
401
+ middle: new RegExp( '(' + rChar + ')(' + rBdMid + ')(' + rChar + ')', 'ig' )
402
+ },
403
+
404
+ zhuyin: {
405
+ form: new RegExp( '^\u02D9?(' + rZyS + ')?(' + rZyJ + ')?(' + rZyY + ')?(' + rZyD + ')?$' ),
406
+ diao: new RegExp( '(' + rZyD + ')', 'g' )
407
+ },
408
+
409
+ /* Hanzi and Western mixed spacing (漢字西文混排間隙)
410
+ * - Basic mode
411
+ * - Strict mode
412
+ */
413
+ hws: {
414
+ base: [
415
+ new RegExp( '('+ rHan +')(' + rAlph + '|' + rPtOpen + ')', 'ig' ),
416
+ new RegExp( '('+ rAlph+ '|' + rPtEnd +')(' + rHan + ')', 'ig' )
417
+ ],
418
+
419
+ strict: [
420
+ new RegExp( '('+ rHan +')' + rWhite + '?(' + rAlph + '|' + rPtOpen + ')', 'ig' ),
421
+ new RegExp( '('+ rAlph+ '|' + rPtEnd +')' + rWhite + '?(' + rHan + ')', 'ig' )
422
+ ]
423
+ },
424
+
425
+ // The feature displays the following characters
426
+ // in its variant form for font consistency and
427
+ // presentational reason. Meanwhile, this won't
428
+ // alter the original character in the DOM.
429
+ 'display-as': {
430
+ 'ja-font-for-hant': [
431
+ // '夠 够',
432
+ '查 査',
433
+ '啟 啓',
434
+ '鄉 鄕',
435
+ '值 値',
436
+ '污 汚'
437
+ ],
438
+
439
+ 'comb-liga-pua': [
440
+ [ '\u0061[\u030d\u0358]', '\uDB80\uDC61' ],
441
+ [ '\u0065[\u030d\u0358]', '\uDB80\uDC65' ],
442
+ [ '\u0069[\u030d\u0358]', '\uDB80\uDC69' ],
443
+ [ '\u006F[\u030d\u0358]', '\uDB80\uDC6F' ],
444
+ [ '\u0075[\u030d\u0358]', '\uDB80\uDC75' ],
445
+
446
+ [ '\u31B4[\u030d\u0358]', '\uDB8C\uDDB4' ],
447
+ [ '\u31B5[\u030d\u0358]', '\uDB8C\uDDB5' ],
448
+ [ '\u31B6[\u030d\u0358]', '\uDB8C\uDDB6' ],
449
+ [ '\u31B7[\u030d\u0358]', '\uDB8C\uDDB7' ]
450
+ ]
451
+ },
452
+
453
+ // The feature actually *converts* the character
454
+ // in the DOM for semantic reason.
455
+ //
456
+ // Note that this could be aggressive.
457
+ 'inaccurate-char': [
458
+ [ '[\u2022\u2027]', '\u00B7' ],
459
+ [ '\u22EF\u22EF', '\u2026\u2026' ],
460
+ [ '\u2500\u2500', '\u2014\u2014' ],
461
+ [ '\u2035', '\u2018' ],
462
+ [ '\u2032', '\u2019' ],
463
+ [ '\u2036', '\u201C' ],
464
+ [ '\u2033', '\u201D' ]
465
+ ]
466
+ }
467
+ })()
468
+
469
+ Han.UNICODE = UNICODE
470
+ Han.TYPESET = TYPESET
471
+
472
+ // Aliases
473
+ Han.UNICODE.cjk = Han.UNICODE.hanzi
474
+ Han.UNICODE.greek = Han.UNICODE.ellinika
475
+ Han.UNICODE.cyrillic = Han.UNICODE.kirillica
476
+ Han.UNICODE.hangul = Han.UNICODE.eonmun
477
+
478
+ Han.TYPESET.char.cjk = Han.TYPESET.char.hanzi
479
+ Han.TYPESET.char.alphabet.greek = Han.TYPESET.char.alphabet.ellinika
480
+ Han.TYPESET.char.alphabet.cyrillic = Han.TYPESET.char.alphabet.kirillica
481
+ Han.TYPESET.char.alphabet.hangul = Han.TYPESET.char.alphabet.eonmun
482
+
483
+ var $ = {
484
+ // Simplified query selectors which return the node list
485
+ // in an array
486
+ id: function( selector, context ) {
487
+ return ( context || document ).getElementById( selector )
488
+ },
489
+
490
+ tag: function( selector, context ) {
491
+ return this.makeArray(
492
+ ( context || document ).getElementsByTagName( selector )
493
+ )
494
+ },
495
+
496
+ qsa: function( selector, context ) {
497
+ return this.makeArray(
498
+ ( context || document ).querySelectorAll( selector )
499
+ )
500
+ },
501
+
502
+ // Create a document fragment, a text node with text
503
+ // or an element with/without classes
504
+ create: function( elem, clazz ) {
505
+ var elem = '!' === elem ?
506
+ document.createDocumentFragment() :
507
+ '' === elem ?
508
+ document.createTextNode( clazz || '' ) :
509
+ document.createElement( elem )
510
+
511
+ try {
512
+ if ( clazz ) {
513
+ elem.className = clazz
514
+ }
515
+ } catch (e) {}
516
+
517
+ return elem
518
+ },
519
+
520
+ // Clone a node (text, element or fragment) deeply or
521
+ // childlessly
522
+ clone: function( node, deep ) {
523
+ return node.cloneNode( deep || true )
524
+ },
525
+
526
+ // Remove a node (text, element or fragment)
527
+ remove: function( node, parent ) {
528
+ return ( parent || node.parentNode ).removeChild( node )
529
+ },
530
+
531
+ // Set attributes all in once with an object
532
+ setAttr: function( target, attr ) {
533
+ if ( typeof attr !== 'object' ) return
534
+ var len = attr.length
535
+
536
+ // Native NamedNodeMap
537
+ if ( typeof attr[ 0 ] === 'object' && 'name' in attr[ 0 ] ) {
538
+ for ( var i = 0; i < len; i++ ) {
539
+ if ( attr[ i ].value !== undefined ) {
540
+ target.setAttribute( attr[ i ].name, attr[ i ].value )
541
+ }
542
+ }
543
+
544
+ // Plain object
545
+ } else {
546
+ for ( var name in attr ) {
547
+ if ( attr.hasOwnProperty( name ) && attr[ name ] !== undefined ) {
548
+ target.setAttribute( name, attr[ name ] )
549
+ }
550
+ }
551
+ }
552
+ return target
553
+ },
554
+
555
+ // Return if the current node should be ignored,
556
+ // `<wbr>` or comments
557
+ isIgnorable: function( node ) {
558
+ return node.nodeName === 'WBR' || node.nodeType === Node.COMMENT_NODE
559
+ },
560
+
561
+ // Convert array-like objects into real arrays
562
+ // for the native prototype methods
563
+ makeArray: function( obj ) {
564
+ return Array.prototype.slice.call( obj )
565
+ },
566
+
567
+ // Extend target with an object
568
+ extend: function( target, object ) {
569
+ var isExtensible = typeof target === 'object' ||
570
+ typeof target === 'function' ||
571
+ typeof object === 'object'
572
+
573
+ if ( !isExtensible ) return
574
+
575
+ for ( var name in object ) {
576
+ if ( object.hasOwnProperty( name )) {
577
+ target[ name ] = object[ name ]
578
+ }
579
+ }
580
+ return target
581
+ }
582
+ }
583
+
584
+ var Fibre =
585
+ /*!
586
+ * Fibre.js v0.1.2 | MIT License | github.com/ethantw/fibre.js
587
+ * Based on findAndReplaceDOMText
588
+ */
589
+
590
+ function( Finder ) {
591
+
592
+ 'use strict'
593
+
594
+ var VERSION = '0.1.2'
595
+ var FILTER_OUT_SELECTOR = 'style, script, head title'
596
+
597
+ var global = window || {}
598
+ var document = global.document || undefined
599
+
600
+ function matches( node, selector, bypassNodeType39 ) {
601
+ var Efn = Element.prototype
602
+ var matches = Efn.matches || Efn.mozMatchesSelector || Efn.msMatchesSelector || Efn.webkitMatchesSelector
603
+
604
+ if ( node instanceof Element ) {
605
+ return matches.call( node, selector )
606
+ } else if ( bypassNodeType39 ) {
607
+ if ( /^[39]$/.test( node.nodeType )) return true
608
+ }
609
+ return false
610
+ }
611
+
612
+ if ( typeof document === 'undefined' ) throw new Error( 'Fibre requires a DOM-supported environment.' )
613
+
614
+ var Fibre = function( context ) {
615
+ return new Fibre.fn.init( context )
616
+ }
617
+
618
+ Fibre.version = VERSION
619
+ Fibre.matches = matches
620
+
621
+ Fibre.fn = Fibre.prototype = {
622
+ constructor: Fibre,
623
+
624
+ version: VERSION,
625
+
626
+ context: undefined,
627
+
628
+ contextSelector: null,
629
+
630
+ finder: [],
631
+
632
+ init: function( context ) {
633
+ if ( !context ) throw new Error( 'A context is required for Fibre to initialise.' )
634
+
635
+ if ( context instanceof Node ) {
636
+ this.context = context
637
+ } else if ( typeof context === 'string' ) {
638
+ this.contextSelector = context
639
+ this.context = document.querySelector( context )
640
+ }
641
+
642
+ return this
643
+ },
644
+
645
+ filterElemFn: function( currentNode ) {
646
+ return matches( currentNode, this.filterSelector, true ) &&
647
+ !matches( currentNode, this.filterOutSelector )
648
+ },
649
+
650
+ filterSelector: '*',
651
+
652
+ filter: function( selector ) {
653
+ switch ( typeof selector ) {
654
+ case 'string':
655
+ this.filterSelector = selector
656
+ break
657
+ case 'function':
658
+ this.filterElemFn = selector
659
+ break
660
+ default:
661
+ return this
662
+ }
663
+ return this
664
+ },
665
+
666
+ filterOutSelector: FILTER_OUT_SELECTOR,
667
+
668
+ filterOut: function( selector, boolExtend ) {
669
+ switch( typeof selector ) {
670
+ case 'string':
671
+ if ( typeof boolExtend !== 'undefined' && boolExtend === true ) {
672
+ this.filterOutSelector += ', ' + selector
673
+ } else {
674
+ this.filterOutSelector = selector
675
+ }
676
+ break
677
+ default:
678
+ return this
679
+ }
680
+ return this
681
+ },
682
+
683
+ replace: function( regexp, newSubStr, portionMode ) {
684
+ var it = this
685
+ var portionMode = portionMode || 'retain'
686
+ it.finder.push(Finder( it.context, {
687
+ find: regexp,
688
+ replace: newSubStr,
689
+ filterElements: function( currentNode ) {
690
+ return it.filterElemFn( currentNode )
691
+ },
692
+ portionMode: portionMode
693
+ }))
694
+ return it
695
+ },
696
+
697
+ wrap: function( regexp, strElemName, portionMode ) {
698
+ var it = this
699
+ var portionMode = portionMode || 'retain'
700
+ it.finder.push(Finder( it.context, {
701
+ find: regexp,
702
+ wrap: strElemName,
703
+ filterElements: function( currentNode ) {
704
+ return it.filterElemFn( currentNode )
705
+ },
706
+ portionMode: portionMode
707
+ }))
708
+ return it
709
+ },
710
+
711
+ revert: function( level ) {
712
+ var max = this.finder.length
713
+ var level = Number( level ) || ( level === 0 ? Number(0) :
714
+ ( level === 'all' ? max : 1 ))
715
+
716
+ if ( typeof max === 'undefined' || max === 0 ) return this
717
+ else if ( level > max ) level = max
718
+
719
+ for ( var i = level; i > 0; i-- ) {
720
+ this.finder.pop().revert()
721
+ }
722
+ return this
723
+ }
724
+ }
725
+
726
+ Fibre.fn.init.prototype = Fibre.fn
727
+
728
+ return Fibre
729
+
730
+ }(
731
+
732
+ /**
733
+ * findAndReplaceDOMText v 0.4.2
734
+ * @author James Padolsey http://james.padolsey.com
735
+ * @license http://unlicense.org/UNLICENSE
736
+ *
737
+ * Matches the text of a DOM node against a regular expression
738
+ * and replaces each match (or node-separated portions of the match)
739
+ * in the specified element.
740
+ */
741
+ (function() {
742
+
743
+ var PORTION_MODE_RETAIN = 'retain'
744
+ var PORTION_MODE_FIRST = 'first'
745
+ var doc = document
746
+ var toString = {}.toString
747
+ function isArray(a) {
748
+ return toString.call(a) == '[object Array]'
749
+ }
750
+
751
+ function escapeRegExp(s) {
752
+ return String(s).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1')
753
+ }
754
+
755
+ function exposed() {
756
+ // Try deprecated arg signature first:
757
+ return deprecated.apply(null, arguments) || findAndReplaceDOMText.apply(null, arguments)
758
+ }
759
+
760
+ function deprecated(regex, node, replacement, captureGroup, elFilter) {
761
+ if ((node && !node.nodeType) && arguments.length <= 2) {
762
+ return false
763
+ }
764
+ var isReplacementFunction = typeof replacement == 'function'
765
+ if (isReplacementFunction) {
766
+ replacement = (function(original) {
767
+ return function(portion, match) {
768
+ return original(portion.text, match.startIndex)
769
+ }
770
+ }(replacement))
771
+ }
772
+
773
+ // Awkward support for deprecated argument signature (<0.4.0)
774
+ var instance = findAndReplaceDOMText(node, {
775
+
776
+ find: regex,
777
+
778
+ wrap: isReplacementFunction ? null : replacement,
779
+ replace: isReplacementFunction ? replacement : '$' + (captureGroup || '&'),
780
+
781
+ prepMatch: function(m, mi) {
782
+
783
+ // Support captureGroup (a deprecated feature)
784
+
785
+ if (!m[0]) throw 'findAndReplaceDOMText cannot handle zero-length matches'
786
+ if (captureGroup > 0) {
787
+ var cg = m[captureGroup]
788
+ m.index += m[0].indexOf(cg)
789
+ m[0] = cg
790
+ }
791
+
792
+ m.endIndex = m.index + m[0].length
793
+ m.startIndex = m.index
794
+ m.index = mi
795
+ return m
796
+ },
797
+ filterElements: elFilter
798
+ })
799
+ exposed.revert = function() {
800
+ return instance.revert()
801
+ }
802
+ return true
803
+ }
804
+
805
+ /**
806
+ * findAndReplaceDOMText
807
+ *
808
+ * Locates matches and replaces with replacementNode
809
+ *
810
+ * @param {Node} node Element or Text node to search within
811
+ * @param {RegExp} options.find The regular expression to match
812
+ * @param {String|Element} [options.wrap] A NodeName, or a Node to clone
813
+ * @param {String|Function} [options.replace='$&'] What to replace each match with
814
+ * @param {Function} [options.filterElements] A Function to be called to check whether to
815
+ * process an element. (returning true = process element,
816
+ * returning false = avoid element)
817
+ */
818
+ function findAndReplaceDOMText(node, options) {
819
+ return new Finder(node, options)
820
+ }
821
+
822
+ exposed.Finder = Finder
823
+ /**
824
+ * Finder -- encapsulates logic to find and replace.
825
+ */
826
+ function Finder(node, options) {
827
+
828
+ options.portionMode = options.portionMode || PORTION_MODE_RETAIN
829
+ this.node = node
830
+ this.options = options
831
+ // ENable match-preparation method to be passed as option:
832
+ this.prepMatch = options.prepMatch || this.prepMatch
833
+ this.reverts = []
834
+ this.matches = this.search()
835
+ if (this.matches.length) {
836
+ this.processMatches()
837
+ }
838
+
839
+ }
840
+
841
+ Finder.prototype = {
842
+
843
+ /**
844
+ * Searches for all matches that comply with the instance's 'match' option
845
+ */
846
+ search: function() {
847
+
848
+ var match
849
+ var matchIndex = 0
850
+ var regex = this.options.find
851
+ var text = this.getAggregateText()
852
+ var matches = []
853
+ regex = typeof regex === 'string' ? RegExp(escapeRegExp(regex), 'g') : regex
854
+ if (regex.global) {
855
+ while (match = regex.exec(text)) {
856
+ matches.push(this.prepMatch(match, matchIndex++))
857
+ }
858
+ } else {
859
+ if (match = text.match(regex)) {
860
+ matches.push(this.prepMatch(match, 0))
861
+ }
862
+ }
863
+
864
+ return matches
865
+ },
866
+
867
+ /**
868
+ * Prepares a single match with useful meta info:
869
+ */
870
+ prepMatch: function(match, matchIndex) {
871
+
872
+ if (!match[0]) {
873
+ throw new Error('findAndReplaceDOMText cannot handle zero-length matches')
874
+ }
875
+
876
+ match.endIndex = match.index + match[0].length
877
+ match.startIndex = match.index
878
+ match.index = matchIndex
879
+ return match
880
+ },
881
+
882
+ /**
883
+ * Gets aggregate text within subject node
884
+ */
885
+ getAggregateText: function() {
886
+
887
+ var elementFilter = this.options.filterElements
888
+ return getText(this.node)
889
+ /**
890
+ * Gets aggregate text of a node without resorting
891
+ * to broken innerText/textContent
892
+ */
893
+ function getText(node) {
894
+
895
+ if (node.nodeType === 3) {
896
+ return node.data
897
+ }
898
+
899
+ if (elementFilter && !elementFilter(node)) {
900
+ return ''
901
+ }
902
+
903
+ var txt = ''
904
+ if (node = node.firstChild) do {
905
+ txt += getText(node)
906
+ } while (node = node.nextSibling)
907
+ return txt
908
+ }
909
+
910
+ },
911
+
912
+ /**
913
+ * Steps through the target node, looking for matches, and
914
+ * calling replaceFn when a match is found.
915
+ */
916
+ processMatches: function() {
917
+
918
+ var matches = this.matches
919
+ var node = this.node
920
+ var elementFilter = this.options.filterElements
921
+ var startPortion,
922
+ endPortion,
923
+ innerPortions = [],
924
+ curNode = node,
925
+ match = matches.shift(),
926
+ atIndex = 0, // i.e. nodeAtIndex
927
+ matchIndex = 0,
928
+ portionIndex = 0,
929
+ doAvoidNode,
930
+ nodeStack = [node]
931
+ out: while (true) {
932
+
933
+ if (curNode.nodeType === 3) {
934
+
935
+ if (!endPortion && curNode.length + atIndex >= match.endIndex) {
936
+
937
+ // We've found the ending
938
+ endPortion = {
939
+ node: curNode,
940
+ index: portionIndex++,
941
+ text: curNode.data.substring(match.startIndex - atIndex, match.endIndex - atIndex),
942
+ indexInMatch: atIndex - match.startIndex,
943
+ indexInNode: match.startIndex - atIndex, // always zero for end-portions
944
+ endIndexInNode: match.endIndex - atIndex,
945
+ isEnd: true
946
+ }
947
+ } else if (startPortion) {
948
+ // Intersecting node
949
+ innerPortions.push({
950
+ node: curNode,
951
+ index: portionIndex++,
952
+ text: curNode.data,
953
+ indexInMatch: atIndex - match.startIndex,
954
+ indexInNode: 0 // always zero for inner-portions
955
+ })
956
+ }
957
+
958
+ if (!startPortion && curNode.length + atIndex > match.startIndex) {
959
+ // We've found the match start
960
+ startPortion = {
961
+ node: curNode,
962
+ index: portionIndex++,
963
+ indexInMatch: 0,
964
+ indexInNode: match.startIndex - atIndex,
965
+ endIndexInNode: match.endIndex - atIndex,
966
+ text: curNode.data.substring(match.startIndex - atIndex, match.endIndex - atIndex)
967
+ }
968
+ }
969
+
970
+ atIndex += curNode.data.length
971
+ }
972
+
973
+ doAvoidNode = curNode.nodeType === 1 && elementFilter && !elementFilter(curNode)
974
+ if (startPortion && endPortion) {
975
+
976
+ curNode = this.replaceMatch(match, startPortion, innerPortions, endPortion)
977
+ // processMatches has to return the node that replaced the endNode
978
+ // and then we step back so we can continue from the end of the
979
+ // match:
980
+
981
+ atIndex -= (endPortion.node.data.length - endPortion.endIndexInNode)
982
+ startPortion = null
983
+ endPortion = null
984
+ innerPortions = []
985
+ match = matches.shift()
986
+ portionIndex = 0
987
+ matchIndex++
988
+ if (!match) {
989
+ break; // no more matches
990
+ }
991
+
992
+ } else if (
993
+ !doAvoidNode &&
994
+ (curNode.firstChild || curNode.nextSibling)
995
+ ) {
996
+ // Move down or forward:
997
+ if (curNode.firstChild) {
998
+ nodeStack.push(curNode)
999
+ curNode = curNode.firstChild
1000
+ } else {
1001
+ curNode = curNode.nextSibling
1002
+ }
1003
+ continue
1004
+ }
1005
+
1006
+ // Move forward or up:
1007
+ while (true) {
1008
+ if (curNode.nextSibling) {
1009
+ curNode = curNode.nextSibling
1010
+ break
1011
+ }
1012
+ curNode = nodeStack.pop()
1013
+ if (curNode === node) {
1014
+ break out
1015
+ }
1016
+ }
1017
+
1018
+ }
1019
+
1020
+ },
1021
+
1022
+ /**
1023
+ * Reverts ... TODO
1024
+ */
1025
+ revert: function() {
1026
+ // Reversion occurs backwards so as to avoid nodes subsequently
1027
+ // replaced during the matching phase (a forward process):
1028
+ for (var l = this.reverts.length; l--;) {
1029
+ this.reverts[l]()
1030
+ }
1031
+ this.reverts = []
1032
+ },
1033
+
1034
+ prepareReplacementString: function(string, portion, match, matchIndex) {
1035
+ var portionMode = this.options.portionMode
1036
+ if (
1037
+ portionMode === PORTION_MODE_FIRST &&
1038
+ portion.indexInMatch > 0
1039
+ ) {
1040
+ return ''
1041
+ }
1042
+ string = string.replace(/\$(\d+|&|`|')/g, function($0, t) {
1043
+ var replacement
1044
+ switch(t) {
1045
+ case '&':
1046
+ replacement = match[0]
1047
+ break
1048
+ case '`':
1049
+ replacement = match.input.substring(0, match.startIndex)
1050
+ break
1051
+ case '\'':
1052
+ replacement = match.input.substring(match.endIndex)
1053
+ break
1054
+ default:
1055
+ replacement = match[+t]
1056
+ }
1057
+ return replacement
1058
+ })
1059
+ if (portionMode === PORTION_MODE_FIRST) {
1060
+ return string
1061
+ }
1062
+
1063
+ if (portion.isEnd) {
1064
+ return string.substring(portion.indexInMatch)
1065
+ }
1066
+
1067
+ return string.substring(portion.indexInMatch, portion.indexInMatch + portion.text.length)
1068
+ },
1069
+
1070
+ getPortionReplacementNode: function(portion, match, matchIndex) {
1071
+
1072
+ var replacement = this.options.replace || '$&'
1073
+ var wrapper = this.options.wrap
1074
+ if (wrapper && wrapper.nodeType) {
1075
+ // Wrapper has been provided as a stencil-node for us to clone:
1076
+ var clone = doc.createElement('div')
1077
+ clone.innerHTML = wrapper.outerHTML || new XMLSerializer().serializeToString(wrapper)
1078
+ wrapper = clone.firstChild
1079
+ }
1080
+
1081
+ if (typeof replacement == 'function') {
1082
+ replacement = replacement(portion, match, matchIndex)
1083
+ if (replacement && replacement.nodeType) {
1084
+ return replacement
1085
+ }
1086
+ return doc.createTextNode(String(replacement))
1087
+ }
1088
+
1089
+ var el = typeof wrapper == 'string' ? doc.createElement(wrapper) : wrapper
1090
+ replacement = doc.createTextNode(
1091
+ this.prepareReplacementString(
1092
+ replacement, portion, match, matchIndex
1093
+ )
1094
+ )
1095
+ if (!replacement.data) {
1096
+ return replacement
1097
+ }
1098
+
1099
+ if (!el) {
1100
+ return replacement
1101
+ }
1102
+
1103
+ el.appendChild(replacement)
1104
+ return el
1105
+ },
1106
+
1107
+ replaceMatch: function(match, startPortion, innerPortions, endPortion) {
1108
+
1109
+ var matchStartNode = startPortion.node
1110
+ var matchEndNode = endPortion.node
1111
+ var preceedingTextNode
1112
+ var followingTextNode
1113
+ if (matchStartNode === matchEndNode) {
1114
+
1115
+ var node = matchStartNode
1116
+ if (startPortion.indexInNode > 0) {
1117
+ // Add `before` text node (before the match)
1118
+ preceedingTextNode = doc.createTextNode(node.data.substring(0, startPortion.indexInNode))
1119
+ node.parentNode.insertBefore(preceedingTextNode, node)
1120
+ }
1121
+
1122
+ // Create the replacement node:
1123
+ var newNode = this.getPortionReplacementNode(
1124
+ endPortion,
1125
+ match
1126
+ )
1127
+ node.parentNode.insertBefore(newNode, node)
1128
+ if (endPortion.endIndexInNode < node.length) { // ?????
1129
+ // Add `after` text node (after the match)
1130
+ followingTextNode = doc.createTextNode(node.data.substring(endPortion.endIndexInNode))
1131
+ node.parentNode.insertBefore(followingTextNode, node)
1132
+ }
1133
+
1134
+ node.parentNode.removeChild(node)
1135
+ this.reverts.push(function() {
1136
+ if (preceedingTextNode === newNode.previousSibling) {
1137
+ preceedingTextNode.parentNode.removeChild(preceedingTextNode)
1138
+ }
1139
+ if (followingTextNode === newNode.nextSibling) {
1140
+ followingTextNode.parentNode.removeChild(followingTextNode)
1141
+ }
1142
+ newNode.parentNode.replaceChild(node, newNode)
1143
+ })
1144
+ return newNode
1145
+ } else {
1146
+ // Replace matchStartNode -> [innerMatchNodes...] -> matchEndNode (in that order)
1147
+
1148
+ preceedingTextNode = doc.createTextNode(
1149
+ matchStartNode.data.substring(0, startPortion.indexInNode)
1150
+ )
1151
+ followingTextNode = doc.createTextNode(
1152
+ matchEndNode.data.substring(endPortion.endIndexInNode)
1153
+ )
1154
+ var firstNode = this.getPortionReplacementNode(
1155
+ startPortion,
1156
+ match
1157
+ )
1158
+ var innerNodes = []
1159
+ for (var i = 0, l = innerPortions.length; i < l; ++i) {
1160
+ var portion = innerPortions[i]
1161
+ var innerNode = this.getPortionReplacementNode(
1162
+ portion,
1163
+ match
1164
+ )
1165
+ portion.node.parentNode.replaceChild(innerNode, portion.node)
1166
+ this.reverts.push((function(portion, innerNode) {
1167
+ return function() {
1168
+ innerNode.parentNode.replaceChild(portion.node, innerNode)
1169
+ }
1170
+ }(portion, innerNode)))
1171
+ innerNodes.push(innerNode)
1172
+ }
1173
+
1174
+ var lastNode = this.getPortionReplacementNode(
1175
+ endPortion,
1176
+ match
1177
+ )
1178
+ matchStartNode.parentNode.insertBefore(preceedingTextNode, matchStartNode)
1179
+ matchStartNode.parentNode.insertBefore(firstNode, matchStartNode)
1180
+ matchStartNode.parentNode.removeChild(matchStartNode)
1181
+ matchEndNode.parentNode.insertBefore(lastNode, matchEndNode)
1182
+ matchEndNode.parentNode.insertBefore(followingTextNode, matchEndNode)
1183
+ matchEndNode.parentNode.removeChild(matchEndNode)
1184
+ this.reverts.push(function() {
1185
+ preceedingTextNode.parentNode.removeChild(preceedingTextNode)
1186
+ firstNode.parentNode.replaceChild(matchStartNode, firstNode)
1187
+ followingTextNode.parentNode.removeChild(followingTextNode)
1188
+ lastNode.parentNode.replaceChild(matchEndNode, lastNode)
1189
+ })
1190
+ return lastNode
1191
+ }
1192
+ }
1193
+
1194
+ }
1195
+ return exposed
1196
+ }())
1197
+ );
1198
+
1199
+ $.extend( Fibre.fn, {
1200
+ // Force punctuation & biaodian typesetting rules to be applied.
1201
+ jinzify: function() {
1202
+ var origFilterOutSelector= this.filterOutSelector
1203
+
1204
+ this.filterOutSelector += ', jinze'
1205
+
1206
+ this
1207
+ .replace(
1208
+ TYPESET.jinze.touwei,
1209
+ function( portion, match ) {
1210
+ var mat = match[0]
1211
+ var text = $.create( '', mat )
1212
+ var elem = $.create( 'jinze', 'touwei' )
1213
+
1214
+ elem.appendChild( text )
1215
+ return (
1216
+ ( portion.index === 0 && portion.isEnd ) || portion.index === 1
1217
+ ) ? elem : ''
1218
+ }
1219
+ )
1220
+ .replace(
1221
+ TYPESET.jinze.wei,
1222
+ function( portion, match ) {
1223
+ var mat = match[0]
1224
+ var text = $.create( '', mat )
1225
+ var elem = $.create( 'jinze', 'wei' )
1226
+
1227
+ elem.appendChild( text )
1228
+ return portion.index === 0 ? elem : ''
1229
+ }
1230
+ )
1231
+ .replace(
1232
+ TYPESET.jinze.tou,
1233
+ function( portion, match ) {
1234
+ var mat = match[0]
1235
+ var text = $.create( '', mat )
1236
+ var elem = $.create( 'jinze', 'tou' )
1237
+
1238
+ elem.appendChild( text )
1239
+ return (
1240
+ ( portion.index === 0 && portion.isEnd ) ||
1241
+ portion.index === 1
1242
+ ) ? elem : ''
1243
+ }
1244
+ )
1245
+ .replace(
1246
+ TYPESET.jinze.middle,
1247
+ function( portion, match ) {
1248
+ var mat = match[0]
1249
+ var text = $.create( '', mat )
1250
+ var elem = $.create( 'jinze', 'middle' )
1251
+
1252
+ elem.appendChild( text )
1253
+ return (( portion.index === 0 && portion.isEnd ) || portion.index === 1 )
1254
+ ? elem : ''
1255
+ }
1256
+ )
1257
+
1258
+ this.filterOutSelector = origFilterOutSelector
1259
+ return this
1260
+ },
1261
+
1262
+ groupify: function() {
1263
+ this
1264
+ .wrap(
1265
+ TYPESET.char.biaodian.group[ 0 ],
1266
+ $.clone( $.create( 'char_group', 'biaodian cjk' ))
1267
+ )
1268
+ .wrap(
1269
+ TYPESET.char.biaodian.group[ 1 ],
1270
+ $.clone( $.create( 'char_group', 'biaodian cjk' ))
1271
+ )
1272
+ return this
1273
+ },
1274
+
1275
+ // Implementation of character-level selector
1276
+ // (字元級選擇器)
1277
+ charify: function( option ) {
1278
+ var option = $.extend({
1279
+ hanzi: 'individual',
1280
+ // individual || group || biaodian || none
1281
+ liga: 'liga',
1282
+ // liga || none
1283
+ word: 'group',
1284
+ // group || punctuation || none
1285
+
1286
+ latin: 'group',
1287
+ ellinika: 'group',
1288
+ kirillica: 'group',
1289
+ kana: 'none',
1290
+ eonmun: 'none'
1291
+ // group || individual || none
1292
+ }, option || {})
1293
+
1294
+ // CJK and biaodian
1295
+ if ( option.hanzi === 'group' ) {
1296
+ this.wrap( TYPESET.char.hanzi.group, $.clone( $.create( 'char_group', 'hanzi cjk' )))
1297
+ }
1298
+ if ( option.hanzi === 'individual' ) {
1299
+ this.wrap( TYPESET.char.hanzi.individual, $.clone( $.create( 'char', 'hanzi cjk' )))
1300
+ }
1301
+
1302
+ if ( option.hanzi === 'individual' ||
1303
+ option.hanzi === 'biaodian' ||
1304
+ option.liga === 'liga'
1305
+ ) {
1306
+ if ( option.hanzi !== 'none' ) {
1307
+ this.replace(
1308
+ TYPESET.char.biaodian.all,
1309
+ function( portion, match ) {
1310
+ var mat = match[0]
1311
+ var text = $.create( '', mat )
1312
+ var clazz = 'biaodian cjk ' + (
1313
+ mat.match( TYPESET.char.biaodian.open ) ? 'open' :
1314
+ mat.match( TYPESET.char.biaodian.close ) ? 'close end' :
1315
+ mat.match( TYPESET.char.biaodian.end ) ? 'end' : ''
1316
+ )
1317
+ var elem = $.create( 'char', clazz )
1318
+ var unicode = mat.charCodeAt( 0 ).toString( 16 )
1319
+
1320
+ elem.setAttribute( 'unicode', unicode )
1321
+ elem.appendChild( text )
1322
+ return elem
1323
+ }
1324
+ )
1325
+ }
1326
+
1327
+ this.replace(
1328
+ option.liga === 'liga' ? TYPESET.char.biaodian.liga :
1329
+ new RegExp( '(' + UNICODE.biaodian.liga + ')', 'g' ),
1330
+ function( portion, match ) {
1331
+ var mat = match[0]
1332
+ var text = $.create( '', mat )
1333
+ var elem = $.create( 'char', 'biaodian liga cjk' )
1334
+ var unicode = mat.charCodeAt( 0 ).toString( 16 )
1335
+
1336
+ elem.setAttribute( 'unicode', unicode )
1337
+ elem.appendChild( text )
1338
+ return elem
1339
+ }
1340
+ )
1341
+ }
1342
+
1343
+ // Western languages (word-level)
1344
+ if ( option.word !== 'none' ) {
1345
+ this.wrap( TYPESET.char.word, $.clone( $.create( 'word' )))
1346
+ }
1347
+
1348
+ // Western languages (alphabet-level)
1349
+ if ( option.latin !== 'none' ||
1350
+ option.ellinika !== 'none' ||
1351
+ option.kirillica !== 'none'
1352
+ ) {
1353
+ this.wrap( TYPESET.char.punct.all, $.clone( $.create( 'char', 'punct' )))
1354
+ }
1355
+ if ( option.latin === 'individual' ) {
1356
+ this.wrap( TYPESET.char.alphabet.latin, $.clone( $.create( 'char', 'alphabet latin' )))
1357
+ }
1358
+ if ( option.ellinika === 'individual' ) {
1359
+ this.wrap( TYPESET.char.alphabet.ellinika, $.clone( $.create( 'char', 'alphabet ellinika greek' )))
1360
+ }
1361
+ if ( option.kirillica === 'individual' ) {
1362
+ this.wrap( TYPESET.char.alphabet.kirillica, $.clone( $.create( 'char', 'alphabet kirillica cyrillic' )))
1363
+ }
1364
+ return this
1365
+ }
1366
+ })
1367
+
1368
+ Han.find = Fibre
1369
+
1370
+ void [
1371
+ 'replace',
1372
+ 'wrap',
1373
+ 'revert',
1374
+ 'jinzify',
1375
+ 'charify'
1376
+ ].forEach(function( method ) {
1377
+ Han.fn[ method ] = function() {
1378
+ if ( !this.finder ) {
1379
+ // Share the same selector
1380
+ this.finder = Han.find( this.context )
1381
+ }
1382
+
1383
+ this.finder[ method ]( arguments[ 0 ], arguments[ 1 ] )
1384
+ return this
1385
+ }
1386
+ })
1387
+
1388
+ var Hyu = {
1389
+ JS_RENDERED_CLASS: 'hyu-js-rendered'
1390
+ }
1391
+
1392
+ function writeOnCanvas( text, font ) {
1393
+ var canvas = $.create( 'canvas' )
1394
+ var context
1395
+
1396
+ canvas.width = '50'
1397
+ canvas.height = '20'
1398
+ canvas.style.display = 'none'
1399
+
1400
+ body.appendChild( canvas )
1401
+
1402
+ context = canvas.getContext( '2d' )
1403
+ context.textBaseline = 'top'
1404
+ context.font = '15px ' + font + ', sans-serif'
1405
+ context.fillStyle = 'black'
1406
+ context.strokeStyle = 'black'
1407
+ context.fillText( text, 0, 0 )
1408
+
1409
+ return [ canvas, context ]
1410
+ }
1411
+
1412
+ function detectFont( treat, control, text ) {
1413
+ var treat = treat
1414
+ var control = control
1415
+ var text = text || '辭Q'
1416
+ var ret
1417
+
1418
+ try {
1419
+ control = writeOnCanvas( text, control || 'sans-serif' )
1420
+ treat = writeOnCanvas( text, treat )
1421
+
1422
+ for ( var j = 1; j <= 20; j++ ) {
1423
+ for ( var i = 1; i <= 50; i++ ) {
1424
+ if (
1425
+ ret !== 'undefined' &&
1426
+ treat[1].getImageData(i, j, 1, 1).data[3] !== control[1].getImageData(i, j, 1, 1).data[3]
1427
+ ) {
1428
+ ret = true
1429
+ break
1430
+ } else if ( ret ) {
1431
+ break
1432
+ }
1433
+
1434
+ if ( i === 50 && j === 20 && !ret ) {
1435
+ ret = false
1436
+ }
1437
+ }
1438
+ }
1439
+
1440
+ // Remove and clean from memory
1441
+ $.remove( control[0] )
1442
+ $.remove( treat[0] )
1443
+ control = null
1444
+ treat = null
1445
+
1446
+ return ret
1447
+ } catch ( e ) {
1448
+ return false
1449
+ }
1450
+ }
1451
+
1452
+ Hyu.detectFont = detectFont
1453
+
1454
+ Hyu.support = (function() {
1455
+
1456
+ var PREFIX = 'Webkit Moz ms'.split(' ')
1457
+
1458
+ // Create an element for feature detecting
1459
+ // (in `testCSSProp`)
1460
+ var elem = $.create( '_' )
1461
+ var exposed = {}
1462
+
1463
+ function testCSSProp( prop ) {
1464
+ var ucProp = prop.charAt(0).toUpperCase() + prop.slice(1)
1465
+ var allProp = ( prop + ' ' + PREFIX.join( ucProp + ' ' ) + ucProp ).split(' ')
1466
+ var ret
1467
+
1468
+ allProp.forEach(function( prop ) {
1469
+ if ( typeof elem.style[ prop ] === 'string' ) {
1470
+ ret = true
1471
+ }
1472
+ })
1473
+ return ret || false
1474
+ }
1475
+
1476
+ function injectElementWithStyle( rule, callback ) {
1477
+ var fakeBody = body || $.create( 'body' )
1478
+ var div = $.create( 'div' )
1479
+ var container = body ? div : fakeBody
1480
+ var callback = typeof callback === 'function' ? callback : function() {}
1481
+ var style, ret, docOverflow
1482
+
1483
+ style = [ '<style>', rule, '</style>' ].join('')
1484
+
1485
+ container.innerHTML += style
1486
+ fakeBody.appendChild( div )
1487
+
1488
+ if ( !body ) {
1489
+ fakeBody.style.background = ''
1490
+ fakeBody.style.overflow = 'hidden'
1491
+ docOverflow = root.style.overflow
1492
+
1493
+ root.style.overflow = 'hidden'
1494
+ root.appendChild( fakeBody )
1495
+ }
1496
+
1497
+ // Callback
1498
+ ret = callback( container, rule )
1499
+
1500
+ // Remove the injected scope
1501
+ $.remove( container )
1502
+ if ( !body ) {
1503
+ root.style.overflow = docOverflow
1504
+ }
1505
+ return !!ret
1506
+ }
1507
+
1508
+ function getStyle( elem, prop ) {
1509
+ var ret
1510
+
1511
+ if ( window.getComputedStyle ) {
1512
+ ret = document.defaultView.getComputedStyle( elem, null ).getPropertyValue( prop )
1513
+ } else if ( elem.currentStyle ) {
1514
+ // for IE
1515
+ ret = elem.currentStyle[ prop ]
1516
+ }
1517
+ return ret
1518
+ }
1519
+
1520
+ return {
1521
+ ruby: (function() {
1522
+ var ruby = $.create( 'ruby' )
1523
+ var rt = $.create( 'rt' )
1524
+ var rp = $.create( 'rp' )
1525
+ var ret
1526
+
1527
+ ruby.appendChild( rp )
1528
+ ruby.appendChild( rt )
1529
+ root.appendChild( ruby )
1530
+
1531
+ // Browsers that support ruby hide the `<rp>` via `display: none`
1532
+ ret = (
1533
+ getStyle( rp, 'display' ) === 'none' ||
1534
+ // but in IE, `<rp>` has `display: inline`
1535
+ // so, the test needs other conditions:
1536
+ getStyle( ruby, 'display' ) === 'ruby' &&
1537
+ getStyle( rt, 'display' ) === 'ruby-text'
1538
+ ) ? true : false
1539
+
1540
+ // Remove and clean from memory
1541
+ root.removeChild( ruby )
1542
+ ruby = null
1543
+ rt = null
1544
+ rp = null
1545
+
1546
+ return ret
1547
+ })(),
1548
+
1549
+ fontface: (function() {
1550
+ var ret
1551
+
1552
+ injectElementWithStyle(
1553
+ '@font-face { font-family: font; src: url("//"); }',
1554
+ function( node, rule ) {
1555
+ var style = $.qsa( 'style', node )[0]
1556
+ var sheet = style.sheet || style.styleSheet
1557
+ var cssText = sheet ?
1558
+ ( sheet.cssRules && sheet.cssRules[0] ?
1559
+ sheet.cssRules[0].cssText : sheet.cssText || ''
1560
+ ) : ''
1561
+
1562
+ ret = /src/i.test( cssText ) &&
1563
+ cssText.indexOf( rule.split(' ')[0] ) === 0
1564
+ }
1565
+ )
1566
+
1567
+ return ret
1568
+ })(),
1569
+
1570
+ // Address feature support test for `unicode-range` via
1571
+ // detecting whether it's Arial (supported) or
1572
+ // Times New Roman (not supported).
1573
+ unicoderange: (function() {
1574
+ var ret
1575
+
1576
+ injectElementWithStyle(
1577
+ '@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}',
1578
+ function() {
1579
+ ret = !Hyu.detectFont(
1580
+ 'test-for-unicode-range', // treatment group
1581
+ 'Arial, "Droid Sans"', // control group
1582
+ 'Q' // ASCII characters only
1583
+ )
1584
+ }
1585
+ )
1586
+ return ret
1587
+ })(),
1588
+
1589
+ columnwidth: testCSSProp( 'columnWidth' ),
1590
+
1591
+ textemphasis: testCSSProp( 'textEmphasis' ),
1592
+
1593
+ writingmode: testCSSProp( 'writingMode' )
1594
+ }
1595
+ })()
1596
+
1597
+ Hyu.initCond = function( target ) {
1598
+ var target = target || root
1599
+ var ret = ''
1600
+ var clazz
1601
+
1602
+ target.classList.add( Hyu.JS_RENDERED_CLASS )
1603
+
1604
+ for ( var feature in Hyu.support ) {
1605
+ clazz = ( Hyu.support[ feature ] ? '' : 'no-' ) + feature
1606
+
1607
+ target.classList.add( clazz )
1608
+ ret += clazz + ' '
1609
+ }
1610
+ return ret
1611
+ }
1612
+
1613
+ /**
1614
+ * Create and return a new `<ru>` element
1615
+ * according to the given contents
1616
+ */
1617
+ function createNormalRu( $rb, $rt, attr ) {
1618
+ var $ru = $.create( 'ru' )
1619
+ var $rt = $.clone( $rt )
1620
+ var attr = attr || {}
1621
+
1622
+ if ( Array.isArray( $rb )) {
1623
+ $ru.innerHTML = $rb.map(function( rb ) {
1624
+ if (typeof rb == 'undefined') { return '' }
1625
+ return rb.outerHTML
1626
+ }).join('')
1627
+ } else {
1628
+ $ru.appendChild( $.clone( $rb ))
1629
+ }
1630
+
1631
+ $ru.appendChild( $rt )
1632
+ attr.annotation = $rt.textContent
1633
+ $.setAttr( $ru, attr )
1634
+ return $ru
1635
+ }
1636
+
1637
+ /**
1638
+ * Create and return a new `<ru>` element
1639
+ * in Zhuyin form
1640
+ */
1641
+ function createZhuyinRu( $rb, $rt ) {
1642
+ var $rb = $.clone( $rb )
1643
+
1644
+ // Create an element to return
1645
+ var $ru = $.create( 'ru' )
1646
+ var $zhuyin = $.create( 'zhuyin' )
1647
+ var $yin = $.create( 'yin' )
1648
+ var $diao = $.create( 'diao' )
1649
+
1650
+ // #### Explanation ####
1651
+ // * `zhuyin`: the entire phonetic annotation
1652
+ // * `yin`: the plain pronunciation (w/out tone)
1653
+ // * `diao`: the tone
1654
+ // * `form`: the combination of the pronunciation
1655
+ // * `len`: the text length of `yin`
1656
+ var zhuyin = $rt.textContent
1657
+ var yin, diao, form, len
1658
+
1659
+ yin = zhuyin.replace( TYPESET.zhuyin.diao, '' )
1660
+ len = yin ? yin.length : 0
1661
+ diao = zhuyin
1662
+ .replace( yin, '' )
1663
+ .replace( /[\u02C5]/g, '\u02C7' )
1664
+ .replace( /[\u030D]/g, '\u0358' )
1665
+
1666
+ form = zhuyin.replace( TYPESET.zhuyin.form, function( s, j, y ) {
1667
+ return [
1668
+ s ? 'S' : null,
1669
+ j ? 'J' : null,
1670
+ y ? 'Y' : null
1671
+ ].join('')
1672
+ })
1673
+ // - <ru>
1674
+ // - <rb><rb/>
1675
+ // - <zhuyin>
1676
+ // - <yin></yin>
1677
+ // - <diao></diao>
1678
+ // - </zhuyin>
1679
+ // - </ru>
1680
+ $diao.innerHTML = diao
1681
+ $yin.innerHTML = yin
1682
+ $zhuyin.appendChild( $yin )
1683
+ $zhuyin.appendChild( $diao )
1684
+
1685
+ $ru.appendChild( $rb )
1686
+ $ru.appendChild( $zhuyin )
1687
+
1688
+ // Finally, set up the necessary attribute
1689
+ // and return the new `<ru>`
1690
+ $.setAttr( $ru, {
1691
+ zhuyin: '',
1692
+ diao: diao,
1693
+ length: len,
1694
+ form: form
1695
+ })
1696
+ return $ru
1697
+ }
1698
+
1699
+ /**
1700
+ * Normalisation rendering mechanism
1701
+ */
1702
+ $.extend( Hyu, {
1703
+
1704
+ // Render and normalise the given context by routine:
1705
+ //
1706
+ // > ruby > u, ins > s, del > em
1707
+ //
1708
+ renderElem: function( context ) {
1709
+ this.renderRuby( context )
1710
+ this.renderDecoLine( context )
1711
+ this.renderDecoLine( context, 's, del' )
1712
+ this.renderEm( context )
1713
+ },
1714
+
1715
+ // Traverse target elements (those with text-decoration
1716
+ // -line) to see if we should address spacing in
1717
+ // between for semantic presentation.
1718
+ renderDecoLine: function( context, target ) {
1719
+ var target = target || 'u, ins'
1720
+ var $target = $.qsa( target, context )
1721
+ var rTarget = new RegExp( '^(' + target.replace(/\,\s?/g, '|') + ')$', 'ig' )
1722
+
1723
+ $target
1724
+ .forEach(function( elem ) {
1725
+ var next
1726
+
1727
+ // Ignore all `<wbr>` and comments in between
1728
+ do {
1729
+ next = ( next || elem ).nextSibling
1730
+ if ( !next ) return
1731
+ } while ( $.isIgnorable( next ))
1732
+
1733
+ if ( next.nodeName.match( rTarget )) {
1734
+ next.classList.add( 'adjacent' )
1735
+ }
1736
+ })
1737
+ },
1738
+
1739
+ // Traverse target elements to render Hanzi emphasis marks
1740
+ // and skip that in punctuation
1741
+ renderEm: function( context, target ) {
1742
+ var method = target ? 'qsa' : 'tag'
1743
+ var target = target || 'em'
1744
+ var $target = $[ method ]( target, context )
1745
+
1746
+ $target
1747
+ .forEach(function( elem ) {
1748
+ var $elem = Fibre( elem )
1749
+
1750
+ if ( !Hyu.support.textemphasis ) {
1751
+ $elem.jinzify()
1752
+ }
1753
+
1754
+ $elem
1755
+ .groupify()
1756
+ .charify( Hyu.support.textemphasis ? {
1757
+ hanzi: 'biaodian',
1758
+ word: 'punctuation'
1759
+ } : {
1760
+ latin: 'individual',
1761
+ ellinika: 'individual',
1762
+ kirillica: 'individual'
1763
+ })
1764
+ })
1765
+ },
1766
+
1767
+ // Address normalisation for both simple and complex
1768
+ // rubies
1769
+ renderRuby: function( context, target ) {
1770
+ var method = target ? 'qsa' : 'tag'
1771
+ var target = target || 'ruby'
1772
+ var $target = $[ method ]( target, context )
1773
+ var $simpClaElem = $.qsa( target + ', rtc', context )
1774
+
1775
+ // First of all, simplify semantic classes
1776
+ $simpClaElem
1777
+ .forEach(function( elem ) {
1778
+ var clazz = elem.classList
1779
+
1780
+ if ( clazz.contains( 'pinyin' )) {
1781
+ clazz.add( 'romanization' )
1782
+ } else if ( clazz.contains( 'mps' )) {
1783
+ clazz.add( 'zhuyin' )
1784
+ }
1785
+
1786
+ if ( clazz.contains( 'romanization' )) {
1787
+ clazz.add( 'annotation' )
1788
+ }
1789
+ })
1790
+
1791
+ // Deal with `<ruby>`
1792
+ $target
1793
+ .forEach(function( ruby ) {
1794
+ var clazz = ruby.classList
1795
+
1796
+ var condition = (
1797
+ !Hyu.support.ruby ||
1798
+ clazz.contains( 'zhuyin') ||
1799
+ clazz.contains( 'complex' ) ||
1800
+ clazz.contains( 'rightangle' )
1801
+ )
1802
+
1803
+ var frag, $cloned, $rb, $ru, maxspan, hruby
1804
+
1805
+ if ( !condition ) return
1806
+
1807
+ // Apply document fragment here to avoid
1808
+ // continuously pointless re-paint
1809
+ frag = $.create( '!' )
1810
+ frag.appendChild( $.clone( ruby ))
1811
+ $cloned = $.qsa( target, frag )[0]
1812
+
1813
+ // 1. Simple ruby polyfill for, um, Firefox;
1814
+ // 2. Zhuyin polyfill for all.
1815
+ if ( !Hyu.support.ruby || clazz.contains( 'zhuyin' )) {
1816
+
1817
+ $
1818
+ .tag( 'rt', $cloned )
1819
+ .forEach(function( rt ) {
1820
+ var $rb = $.create( '!' )
1821
+ var airb = []
1822
+ var irb
1823
+
1824
+ // Consider the previous nodes the implied
1825
+ // ruby base
1826
+ do {
1827
+ irb = ( irb || rt ).previousSibling
1828
+
1829
+ if ( !irb || irb.nodeName.match( /(r[ubt])/i )) break
1830
+
1831
+ $rb.insertBefore( $.clone( irb ), $rb.firstChild )
1832
+ airb.push( irb )
1833
+ } while ( !irb.nodeName.match( /(r[ubt])/i ))
1834
+ // Create a real `<ru>` to append.
1835
+ $ru = clazz.contains( 'zhuyin' ) ?
1836
+ createZhuyinRu( $rb, rt ) : createNormalRu( $rb, rt )
1837
+
1838
+ // Replace the ruby text with the new `<ru>`,
1839
+ // and remove the original implied ruby base(s)
1840
+ try {
1841
+ rt.parentNode.replaceChild( $ru, rt )
1842
+
1843
+ airb
1844
+ .forEach(function( irb ) {
1845
+ $.remove( irb )
1846
+ })
1847
+ } catch ( e ) {}
1848
+ })
1849
+ }
1850
+
1851
+ // 3. Complex ruby polyfill
1852
+ // - Double-lined annotation;
1853
+ // - Right-angled annotation.
1854
+ if ( clazz.contains( 'complex' ) || clazz.contains( 'rightangle' )) {
1855
+ $rb = $ru = $.tag( 'rb', $cloned )
1856
+ maxspan = $rb.length
1857
+
1858
+ // First of all, deal with Zhuyin containers
1859
+ // individually
1860
+ //
1861
+ // Note that we only support one single Zhuyin
1862
+ // container in each complex ruby
1863
+ !function( rtc ) {
1864
+ if ( !rtc ) return
1865
+
1866
+ $ru = $
1867
+ .tag( 'rt', rtc )
1868
+ .map(function( rt, i ) {
1869
+ if ( !$rb[ i ] ) return
1870
+ var ret = createZhuyinRu( $rb[ i ], rt )
1871
+
1872
+ try {
1873
+ $rb[ i ].parentNode.replaceChild( ret, $rb[ i ] )
1874
+ } catch ( e ) {}
1875
+ return ret
1876
+ })
1877
+
1878
+ // Remove the container once it's useless
1879
+ $.remove( rtc )
1880
+ ruby.setAttribute( 'rightangle', '' )
1881
+ }( $cloned.querySelector( 'rtc.zhuyin' ))
1882
+
1883
+ // Then, normal annotations other than Zhuyin
1884
+ $
1885
+ .qsa( 'rtc:not(.zhuyin)', $cloned )
1886
+ .forEach(function( rtc, order ) {
1887
+ var ret
1888
+ ret = $
1889
+ .tag( 'rt', rtc )
1890
+ .map(function( rt, i ) {
1891
+ var rbspan = Number( rt.getAttribute( 'rbspan' ) || 1 )
1892
+ var span = 0
1893
+ var aRb = []
1894
+ var rb, ret
1895
+
1896
+ if ( rbspan > maxspan ) {
1897
+ rbspan = maxspan
1898
+ }
1899
+
1900
+ do {
1901
+ try {
1902
+ rb = $ru.shift()
1903
+ aRb.push( rb )
1904
+ } catch (e) {}
1905
+ if (typeof rb == 'undefined') { break }
1906
+ span += Number( rb.getAttribute( 'span' ) || 1 )
1907
+ } while ( rbspan > span )
1908
+
1909
+ if ( rbspan < span ) {
1910
+ if ( aRb.length > 1 ) {
1911
+ console.error( 'An impossible `rbspan` value detected.', ruby )
1912
+ return
1913
+ }
1914
+ aRb = $.tag( 'rb', aRb[0] )
1915
+ $ru = aRb.slice( rbspan ).concat( $ru )
1916
+ aRb = aRb.slice( 0, rbspan )
1917
+ span = rbspan
1918
+ }
1919
+
1920
+ ret = createNormalRu( aRb, rt, {
1921
+ 'class': clazz,
1922
+ span: span,
1923
+ order: order
1924
+ })
1925
+
1926
+ try {
1927
+ aRb[0].parentNode.replaceChild( ret, aRb.shift())
1928
+ aRb.forEach(function( rb ) {
1929
+ $.remove( rb )
1930
+ })
1931
+ } catch (e) {}
1932
+
1933
+ return ret
1934
+ })
1935
+ $ru = ret
1936
+ // Remove the container once it's useless
1937
+ $.remove( rtc )
1938
+ })
1939
+ }
1940
+ // Create a new fake `<hruby>` element so the
1941
+ // style sheets will render it as a polyfill,
1942
+ // which also helps to avoid the UA style.
1943
+ //
1944
+ // (The ‘H’ stands for ‘Han’, by the way)
1945
+ hruby = $.create( 'hruby' )
1946
+ hruby.innerHTML = frag.firstChild.innerHTML
1947
+
1948
+ // Copy all attributes onto it
1949
+ $.setAttr( hruby, ruby.attributes )
1950
+ hruby.normalize()
1951
+
1952
+ // Finally, replace it
1953
+ ruby.parentNode.replaceChild( hruby, ruby )
1954
+ })
1955
+ }
1956
+
1957
+ // ### TODO list ###
1958
+ //
1959
+ // * Debug mode
1960
+ // * Better error-tolerance
1961
+ })
1962
+
1963
+ /*!
1964
+ * Hyu
1965
+ * css.hanzi.co/hyu
1966
+ *
1967
+ * This module is a subset project of Han,
1968
+ * which aims to provide HTML5-ready and
1969
+ * Hanzi-optimised style normalisation.
1970
+ */
1971
+
1972
+ Han.normalize = Hyu
1973
+ Han.support = Hyu.support
1974
+ Han.detectFont = Hyu.detectFont
1975
+
1976
+ Han.fn.initCond = function() {
1977
+ this.condition.classList.add( 'han-js-rendered' )
1978
+ Han.normalize.initCond( this.condition )
1979
+ return this
1980
+ }
1981
+
1982
+ void [
1983
+ 'Elem',
1984
+ 'DecoLine',
1985
+ 'Em',
1986
+ 'Ruby'
1987
+ ].forEach(function( elem ) {
1988
+ var method = 'render' + elem
1989
+
1990
+ Han.fn[ method ] = function( target ) {
1991
+ Han.normalize[ method ]( this.context, target )
1992
+ return this
1993
+ }
1994
+ })
1995
+
1996
+ $.extend( Han.support, {
1997
+ // Assume that all devices support Heiti for we
1998
+ // use `sans-serif` to do the comparison.
1999
+ heiti: true,
2000
+ // 'heiti-gb': true,
2001
+
2002
+ songti: Han.detectFont( '"Han Songti"' ),
2003
+ 'songti-gb': Han.detectFont( '"Han Songti GB"' ),
2004
+
2005
+ kaiti: Han.detectFont( '"Han Kaiti"' ),
2006
+ // 'kaiti-gb': Han.detectFont( '"Han Kaiti GB"' ),
2007
+
2008
+ fangsong: Han.detectFont( '"Han Fangsong"' )
2009
+ // 'fangsong-gb': Han.detectFont( '"Han Fangsong GB"' )
2010
+ })
2011
+
2012
+ var QUERY_HWS_AS_FIRST_CHILD = '* > hws:first-child, * > wbr:first-child + hws, wbr:first-child + wbr + hws'
2013
+
2014
+ //// Disabled `Node.normalize()` for temp due to
2015
+ //// issue below in IE11.
2016
+ //// See: http://stackoverflow.com/questions/22337498/why-does-ie11-handle-node-normalize-incorrectly-for-the-minus-symbol
2017
+ var isNodeNormalizeNormal = (function() {
2018
+ var div = $.create( 'div' )
2019
+
2020
+ div.appendChild( $.create( '', '0-' ))
2021
+ div.appendChild( $.create( '', '2' ))
2022
+ div.normalize()
2023
+
2024
+ return div.firstChild.length !== 2
2025
+ })(),
2026
+
2027
+ hws
2028
+
2029
+ hws = $.create( 'hws' )
2030
+ hws.innerHTML = ' '
2031
+
2032
+ $.extend( Han, {
2033
+ isNodeNormalizeNormal: isNodeNormalizeNormal,
2034
+
2035
+ renderHWS: function( context, strict ) {
2036
+ var context = context || document
2037
+ var mode = strict ? 'strict' : 'base'
2038
+ var finder = Han.find( context )
2039
+
2040
+ // Elements to be filtered according to the
2041
+ // HWS rendering mode
2042
+ if ( strict ) {
2043
+ finder.filterOut( 'textarea, code, kbd, samp, pre', true )
2044
+ } else {
2045
+ finder.filterOut( 'textarea', true )
2046
+ }
2047
+
2048
+ finder
2049
+ .replace( Han.TYPESET.hws[ mode ][0], '$1<hws/>$2' )
2050
+ .replace( Han.TYPESET.hws[ mode ][1], '$1<hws/>$2' )
2051
+
2052
+ // Deal with `' 字'`, `" 字"` => `'字'`, `"字"`
2053
+ .replace( /(['"]+)<hws\/>(.+?)<hws\/>\1/ig, '$1$2$1' )
2054
+
2055
+ // Convert text nodes `<hws/>` into real element nodes
2056
+ .replace( '<hws/>', function() {
2057
+ return $.clone( hws )
2058
+ })
2059
+
2060
+ // Deal with:
2061
+ // `漢<u><hws/>zi</u>` => `漢<hws/><u>zi</u>`
2062
+ $
2063
+ .qsa( QUERY_HWS_AS_FIRST_CHILD, context )
2064
+ .forEach(function( firstChild ) {
2065
+ var parent = firstChild.parentNode
2066
+ var target = parent.firstChild
2067
+
2068
+ // Skip all `<wbr>` and comments
2069
+ while ( $.isIgnorable( target )) {
2070
+ target = target.nextSibling
2071
+
2072
+ if ( !target ) return
2073
+ }
2074
+
2075
+ // The ‘first-child’ of DOM is different from
2076
+ // the ones of QSA, could be either an element
2077
+ // or a text fragment, but the latter one is
2078
+ // not what we want. We don't want comments,
2079
+ // either.
2080
+ while ( target.nodeName === 'HWS' ) {
2081
+ $.remove( target, parent )
2082
+
2083
+ target = parent.parentNode.insertBefore( $.clone( hws ), parent )
2084
+ parent = parent.parentNode
2085
+
2086
+ if ( isNodeNormalizeNormal ) {
2087
+ parent.normalize()
2088
+ }
2089
+
2090
+ // This is for extreme circumstances, i.e.,
2091
+ // `漢<a><b><c><hws/>zi</c></b></a>` =>
2092
+ // `漢<hws/><a><b><c>zi</c></b></a>`
2093
+ if ( target !== parent.firstChild ) {
2094
+ break
2095
+ }
2096
+ }
2097
+ })
2098
+
2099
+ // Normalise nodes we messed up with
2100
+ if ( isNodeNormalizeNormal ) {
2101
+ context.normalize()
2102
+ }
2103
+
2104
+ // Return the finder instance for future usage
2105
+ return finder
2106
+ }
2107
+ })
2108
+
2109
+ $.extend( Han.fn, {
2110
+ HWS: null,
2111
+
2112
+ renderHWS: function( strict ) {
2113
+ Han.renderHWS( this.context, strict )
2114
+
2115
+ this.HWS = $.tag( 'hws', this.context )
2116
+ return this
2117
+ },
2118
+
2119
+ revertHWS: function() {
2120
+ this.HWS.forEach(function( hws ) {
2121
+ $.remove( hws )
2122
+ })
2123
+ return this
2124
+ }
2125
+ })
2126
+
2127
+ Han.renderJiya = function( context ) {
2128
+ var context = context || document
2129
+ var finder = [ Han.find( context ) ]
2130
+
2131
+ finder[ 0 ].filterOut( 'textarea, code, kbd, samp, pre, jinze, em', true )
2132
+ finder[ 0 ].groupify()
2133
+
2134
+ $
2135
+ .qsa( 'char_group.biaodian', context )
2136
+ .forEach(function( elem ) {
2137
+ finder.push(
2138
+ Han( elem )
2139
+ .charify({
2140
+ hanzi: 'biaodian',
2141
+ liga: 'liga',
2142
+ word: 'none',
2143
+ latin: 'none',
2144
+ ellinika: 'none',
2145
+ kirillica: 'none'
2146
+ })
2147
+ )
2148
+ })
2149
+ return finder
2150
+ }
2151
+
2152
+ $.extend( Han.fn, {
2153
+ jiya: null,
2154
+
2155
+ renderJiya: function() {
2156
+ this.jiya = Han.renderJiya( this.context )
2157
+ return this
2158
+ },
2159
+
2160
+ revertJiya: function() {
2161
+ try {
2162
+ this.jiya.revert( 'all' )
2163
+ } catch ( e ) {}
2164
+ return this
2165
+ }
2166
+ })
2167
+
2168
+ var mdot
2169
+
2170
+ mdot = $.create( 'char', 'biaodian cjk middle' )
2171
+ mdot.setAttribute( 'unicode', 'b7' )
2172
+
2173
+ Han.correctBasicBD = function( context, all ) {
2174
+ if ( Han.support.unicoderange && !all ) return
2175
+
2176
+ var context = context || document
2177
+ var finder
2178
+
2179
+ finder = Han.find( context )
2180
+
2181
+ finder
2182
+ .wrap( /\u00B7/g, $.clone( mdot ))
2183
+ .charify({
2184
+ liga: 'liga',
2185
+ hanzi: 'none',
2186
+ word: 'none',
2187
+ latin: 'none',
2188
+ ellinika: 'none',
2189
+ kirillica: 'none'
2190
+ })
2191
+ }
2192
+
2193
+ $.extend( Han.fn, {
2194
+ basicBD: null,
2195
+
2196
+ correctBasicBD: function( all ) {
2197
+ this.basicBD = Han.correctBasicBD( this.context, all )
2198
+ return this
2199
+ },
2200
+
2201
+ revertBasicBD: function() {
2202
+ try {
2203
+ this.basicBD.revert( 'all' )
2204
+ } catch (e) {}
2205
+ return this
2206
+ }
2207
+ })
2208
+
2209
+ var QUERY_RU_W_ANNO = 'ru[annotation]'
2210
+ var SELECTOR_TO_IGNORE = 'textarea, code, kbd, samp, pre'
2211
+
2212
+ var isCombLigaNormal = (function() {
2213
+ var fakeBody = body || $.create( 'body' )
2214
+ var div = $.create( 'div' )
2215
+ var control = $.create( 'span' )
2216
+ var container = body ? div : fakeBody
2217
+ var treat, docOverflow, ret
2218
+
2219
+ if ( !body ) {
2220
+ fakeBody.style.background = ''
2221
+ fakeBody.style.overflow = 'hidden'
2222
+ docOverflow = root.style.overflow
2223
+
2224
+ root.style.overflow = 'hidden'
2225
+ root.appendChild( fakeBody )
2226
+ } else {
2227
+ body.appendChild( container )
2228
+ }
2229
+
2230
+ control.innerHTML = '&#x0069;&#x030D;'
2231
+ control.style.fontFamily = 'sans-serif'
2232
+ control.style.display = 'inline-block'
2233
+
2234
+ treat = $.clone( control )
2235
+ treat.style.fontFamily = '"Romanization Sans"'
2236
+
2237
+ container.appendChild( control )
2238
+ container.appendChild( treat )
2239
+
2240
+ ret = control.clientWidth !== treat.clientWidth
2241
+ $.remove( container )
2242
+
2243
+ if ( !body ) {
2244
+ root.style.overflow = docOverflow
2245
+ }
2246
+ return ret
2247
+ })()
2248
+
2249
+ var aCombLiga = Han.TYPESET[ 'display-as' ][ 'comb-liga-pua' ]
2250
+ var aInaccurateChar = Han.TYPESET[ 'inaccurate-char' ]
2251
+
2252
+ var charCombLiga = $.create( 'char', 'comb-liga' )
2253
+ var charCombLigaInner = $.create( 'inner' )
2254
+
2255
+ $.extend( Han, {
2256
+ isCombLigaNormal: isCombLigaNormal,
2257
+
2258
+ substCombLigaWithPUA: function( context ) {
2259
+ if ( isCombLigaNormal ) return
2260
+
2261
+ var context = context || document
2262
+ var finder = Han.find( context )
2263
+
2264
+ finder.filterOut( SELECTOR_TO_IGNORE, true )
2265
+
2266
+ aCombLiga
2267
+ .forEach(function( pattern ) {
2268
+ finder
2269
+ .replace(
2270
+ new RegExp( pattern[ 0 ], 'ig' ),
2271
+ function( portion, match ) {
2272
+ var ret = $.clone( charCombLiga )
2273
+ var inner = $.clone( charCombLigaInner )
2274
+
2275
+ // Put the original content in an inner container
2276
+ // for better presentational effect of hidden text
2277
+ inner.innerHTML = match[ 0 ]
2278
+ ret.appendChild( inner )
2279
+ ret.setAttribute( 'display-as', pattern[ 1 ] )
2280
+ return portion.index === 0 ? ret : ''
2281
+ }
2282
+ )
2283
+ })
2284
+
2285
+ $
2286
+ .qsa( QUERY_RU_W_ANNO, context )
2287
+ .forEach(function( ru ) {
2288
+ var annotation = ru.getAttribute( 'annotation' )
2289
+
2290
+ aCombLiga
2291
+ // Latin vowels only
2292
+ .slice( 0, 5 )
2293
+ .forEach(function( pattern ) {
2294
+ annotation = annotation.replace(
2295
+ new RegExp( pattern[ 0 ], 'ig' ), pattern[ 1 ]
2296
+ )
2297
+ })
2298
+ ru.setAttribute( 'annotation', annotation )
2299
+ })
2300
+ return finder
2301
+ },
2302
+
2303
+ substInaccurateChar: function( context ) {
2304
+ var context = context || document
2305
+ var finder = Han.find( context )
2306
+
2307
+ finder.filterOut( SELECTOR_TO_IGNORE, true )
2308
+ aInaccurateChar
2309
+ .forEach(function( pattern ) {
2310
+ finder
2311
+ .replace(
2312
+ new RegExp( pattern[ 0 ], 'ig' ),
2313
+ pattern[ 1 ]
2314
+ )
2315
+ })
2316
+ }
2317
+ })
2318
+
2319
+ $.extend( Han.fn, {
2320
+ 'comb-liga': null,
2321
+ 'inaccurate-char': null,
2322
+
2323
+ substCombLigaWithPUA: function() {
2324
+ this['comb-liga'] = Han.substCombLigaWithPUA( this.context )
2325
+ return this
2326
+ },
2327
+
2328
+ revertCombLigaWithPUA: function() {
2329
+ try {
2330
+ this['comb-liga'].revert( 'all' )
2331
+ } catch (e) {}
2332
+ return this
2333
+ },
2334
+
2335
+ substInaccurateChar: function() {
2336
+ this['inaccurate-char'] = Han.substInaccurateChar( this.context )
2337
+ return this
2338
+ },
2339
+
2340
+ revertInaccurateChar: function() {
2341
+ try {
2342
+ this['inaccurate-char'].revert( 'all' )
2343
+ } catch (e) {}
2344
+ return this
2345
+ }
2346
+ })
2347
+
2348
+ window.addEventListener( 'DOMContentLoaded', function() {
2349
+ var initContext
2350
+
2351
+ // Use the shortcut under the default situation
2352
+ if ( root.classList.contains( 'han-init' )) {
2353
+ Han.init()
2354
+
2355
+ // Consider ‘a configured context’ the special
2356
+ // case of the default situation. Will have to
2357
+ // replace the `Han.init` with the instance as
2358
+ // well (for future usage).
2359
+ } else if ( initContext = document.querySelector( '.han-init-context' )) {
2360
+ Han.init = Han( initContext ).render()
2361
+ }
2362
+ })
2363
+
2364
+ // AMD
2365
+ if ( typeof define === 'function' && define.amd ) {
2366
+ define(function() { return Han })
2367
+
2368
+ // Expose to global namespace
2369
+ } else if ( typeof noGlobalNS === 'undefined' || noGlobalNS === false ) {
2370
+ window.Han = Han
2371
+ }
2372
+
2373
+ return Han
2374
+ });
2375
+