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.
- checksums.yaml +4 -4
- data/{vendor/assets/stylesheets/font → app/assets/fonts}/han.otf +0 -0
- data/{vendor/assets/stylesheets/font → app/assets/fonts}/han.ttf +0 -0
- data/{vendor/assets/stylesheets/font → app/assets/fonts}/han.woff +0 -0
- data/app/assets/javascripts/hanzi.js +2375 -0
- data/app/assets/stylesheets/hanzi.css.erb +2283 -0
- data/lib/hanzi-rails/engine.rb +6 -0
- data/lib/{hanzi/rails → hanzi-rails}/version.rb +1 -1
- data/lib/hanzi-rails.rb +2 -8
- metadata +18 -11
- data/vendor/assets/javascripts/hanzi.js +0 -4
- data/vendor/assets/stylesheets/hanzi.css +0 -6
@@ -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 = 'i̍'
|
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
|
+
|