hanzi-rails 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
+