hanzi-rails 0.0.5 → 0.0.6

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 46b145f590f5c24e09a3f3804b09536a76dabf40
4
- data.tar.gz: b8a2e394816d69b778781ef870c08a5921cd5d8f
3
+ metadata.gz: d6432afbb2c17e412416668ff9b631cf77cb2d4e
4
+ data.tar.gz: 15a05a57491228baa576a452076676a9581bd422
5
5
  SHA512:
6
- metadata.gz: e84c85c534e3a08f113b056ffed19b1957b2836ab479688e83c3b07279102a12c6a2da3a9729ab5c0867906317552a59025dad2c3cfe5c8ef4325af9f4184d84
7
- data.tar.gz: 56ef1f99ab26af5623bc0561fcb1eb1024390e6d30d417a63757014ca18340e524b112121f2243ccc607f643e67f851f86bdbe64e9df75afa0bc378913b14dd4
6
+ metadata.gz: 92a9340628820db6d06759af2cd8bda137f09e429784c3128de8c04ee60c03ed96371fc6a9ae9c3c8f8f5df92a4347166f5e3e9f5dc098ead3322bcb76da2dce
7
+ data.tar.gz: 893ee219a9dff560f6ed8c35725c1f1fb50bde37435ec9a1dd1e312d0431aeae1481604483d1b3224f53df579235610a723c95cf2624daccff8bd1c3cf8c9d39
data/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  This is a rails gem which provides Sass/JavaScript typesetting framework Han.css.
4
4
 
5
- You can read more about han.css at [here](https://github.com/ethantw/Han/)
5
+ You can read more about han.css at [here](https://github.com/ethantw/Han/) and read manual at [here](https://css.hanzi.co/manual/)
6
6
 
7
7
  han.css is made by Chen Yijun (陳奕鈞,http://yijun.me/).
8
8
 
@@ -30,12 +30,22 @@ The han.css files will be added to the asset pipeline and available for you to u
30
30
  *= require hanzi
31
31
  ```
32
32
 
33
- If you need to use the javascript render function, Add the line to `app/assets/javascripts/application.js`:
33
+ If you need to use the javascript render function, Add these lines to `app/assets/javascripts/application.js`:
34
34
 
35
35
  ```js
36
36
  //= require hanzi
37
+
38
+ Han.init();
37
39
  ```
38
40
 
41
+ And add class `han-init` and correct lang attribute to `html` tag.
42
+
43
+ ```html
44
+ <html lang="zh-Hant" class="han-init">
45
+ ```
46
+
47
+ For more information, please read the [manual](https://css.hanzi.co/manual/js-api#rendering).
48
+
39
49
  ## Contributing
40
50
 
41
51
  1. Fork it ( https://github.com/billy3321/hanzi-rails/fork )
File without changes
@@ -1,5 +1,5 @@
1
1
  /*!
2
- * 漢字標準格式 v3.2.7 | MIT License | css.hanzi.co
2
+ * 漢字標準格式 v3.3.0 | MIT License | css.hanzi.co
3
3
  * Han.css: the CSS typography framework optimised for Hanzi
4
4
  */
5
5
 
@@ -9,7 +9,7 @@ void function( global, factory ) {
9
9
  if ( typeof module === 'object' && typeof module.exports === 'object' ) {
10
10
  module.exports = factory( global, true )
11
11
  // AMD
12
- } else if ( typeof define === 'function' && define.amd ) {
12
+ } else if ( typeof define === 'function' && define.amd ) {
13
13
  define(function() { return factory( global, true ) })
14
14
  // Global namespace
15
15
  } else {
@@ -26,7 +26,7 @@ var root = document.documentElement
26
26
 
27
27
  var body = document.body
28
28
 
29
- var VERSION = '3.2.7'
29
+ var VERSION = '3.3.0'
30
30
 
31
31
  var ROUTINE = [
32
32
  // Initialise the condition with feature-detecting
@@ -39,15 +39,15 @@ var ROUTINE = [
39
39
 
40
40
  // Handle Biaodian
41
41
  /* 'jinzify', */
42
- 'renderHanging',
43
42
  'renderJiya',
43
+ 'renderHanging',
44
+
45
+ // Address Biaodian correction
46
+ 'correctBiaodian',
44
47
 
45
48
  // Address Hanzi and Western script mixed spacing
46
49
  'renderHWS',
47
50
 
48
- // Address Basic Biaodian correction in Firefox
49
- 'correctBasicBD',
50
-
51
51
  // Address presentational correction to combining ligatures
52
52
  'substCombLigaWithPUA'
53
53
 
@@ -101,17 +101,23 @@ Han.fn = Han.prototype = {
101
101
  // the instance or in the prototype chain.
102
102
  render: function( routine ) {
103
103
  var it = this
104
- var routine = Array.isArray( routine ) ? routine : this.routine
104
+ var routine = Array.isArray( routine )
105
+ ? routine
106
+ : this.routine
105
107
 
106
108
  routine
107
109
  .forEach(function( method ) {
108
- try {
109
- if ( typeof method === 'string' ) {
110
- it[ method ]()
111
- } else if ( Array.isArray( method )) {
112
- it[ method.shift() ].apply( it, method )
113
- }
114
- } catch ( e ) {}
110
+ if (
111
+ typeof method === 'string' &&
112
+ typeof it[ method ] === 'function'
113
+ ) {
114
+ it[ method ]()
115
+ } else if (
116
+ Array.isArray( method ) &&
117
+ typeof it[ method[0] ] === 'function'
118
+ ) {
119
+ it[ method.shift() ].apply( it, method )
120
+ }
115
121
  })
116
122
  return this
117
123
  }
@@ -325,31 +331,31 @@ var TYPESET = (function() {
325
331
  var rPtMid = UNICODE.punct.middle
326
332
  var rPtSing = UNICODE.punct.sing
327
333
  var rPt = rPtOpen + '|' + rPtEnd + '|' + rPtMid
328
-
329
- var rBdOpen = UNICODE.biaodian.open
330
- var rBdClose = UNICODE.biaodian.close
331
- var rBdEnd = UNICODE.biaodian.end
332
- var rBdMid = UNICODE.biaodian.middle
333
- var rBdLiga = UNICODE.biaodian.liga + '{2}'
334
- var rBd = rBdOpen + '|' + rBdEnd + '|' + rBdMid
335
-
334
+
335
+ var rBDOpen = UNICODE.biaodian.open
336
+ var rBDClose = UNICODE.biaodian.close
337
+ var rBDEnd = UNICODE.biaodian.end
338
+ var rBDMid = UNICODE.biaodian.middle
339
+ var rBDLiga = UNICODE.biaodian.liga + '{2}'
340
+ var rBD = rBDOpen + '|' + rBDEnd + '|' + rBDMid
341
+
336
342
  var rKana = UNICODE.kana.base + UNICODE.kana.combine + '?'
337
343
  var rKanaS = UNICODE.kana.small + UNICODE.kana.combine + '?'
338
344
  var rKanaH = UNICODE.kana.half
339
345
  var rEon = UNICODE.eonmun.base + '|' + UNICODE.eonmun.letter
340
346
  var rEonH = UNICODE.eonmun.half
341
-
347
+
342
348
  var rHan = UNICODE.hanzi.base + '|' + UNICODE.hanzi.desc + '|' + UNICODE.hanzi.radical + '|' + rKana
343
-
349
+
344
350
  var rCbn = UNICODE.ellinika.combine
345
351
  var rLatn = UNICODE.latin.base + rCbn + '*'
346
352
  var rGk = UNICODE.ellinika.base + rCbn + '*'
347
-
353
+
348
354
  var rCyCbn = UNICODE.kirillica.combine
349
355
  var rCy = UNICODE.kirillica.base + rCyCbn + '*'
350
-
356
+
351
357
  var rAlph = rLatn + '|' + rGk + '|' + rCy
352
-
358
+
353
359
  // For words like `it's`, `Jones’s` or `'99`
354
360
  var rApo = '[\u0027\u2019]'
355
361
  var rChar = rHan + '|(?:' + rAlph + '|' + rApo + ')+'
@@ -371,29 +377,29 @@ var TYPESET = (function() {
371
377
  },
372
378
 
373
379
  biaodian: {
374
- all: new RegExp( '(' + rBd + ')', 'g' ),
375
- open: new RegExp( '(' + rBdOpen + ')', 'g' ),
376
- close: new RegExp( '(' + rBdClose + ')', 'g' ),
377
- end: new RegExp( '(' + rBdEnd + ')', 'g' ),
378
- liga: new RegExp( '(' + rBdLiga + ')', 'g' )
380
+ all: new RegExp( '(' + rBD + ')', 'g' ),
381
+ open: new RegExp( '(' + rBDOpen + ')', 'g' ),
382
+ close: new RegExp( '(' + rBDClose + ')', 'g' ),
383
+ end: new RegExp( '(' + rBDEnd + ')', 'g' ),
384
+ liga: new RegExp( '(' + rBDLiga + ')', 'g' )
379
385
  },
380
386
 
381
- hanzi: new RegExp( '(' + rHan + ')', 'g' ),
387
+ hanzi: new RegExp( '(' + rHan + ')', 'g' ),
382
388
 
383
- latin: new RegExp( '(' + rLatn + ')', 'ig' ),
384
- ellinika: new RegExp( '(' + rGk + ')', 'ig' ),
385
- kirillica: new RegExp( '(' + rCy + ')', 'ig' ),
389
+ latin: new RegExp( '(' + rLatn + ')', 'ig' ),
390
+ ellinika: new RegExp( '(' + rGk + ')', 'ig' ),
391
+ kirillica: new RegExp( '(' + rCy + ')', 'ig' ),
386
392
 
387
- kana: new RegExp( '(' + rKana + '|' + rKanaS + '|' + rKanaH + ')', 'g' ),
388
- eonmun: new RegExp( '(' + rEon + '|' + rEonH + ')', 'g' )
393
+ kana: new RegExp( '(' + rKana + '|' + rKanaS + '|' + rKanaH + ')', 'g' ),
394
+ eonmun: new RegExp( '(' + rEon + '|' + rEonH + ')', 'g' )
389
395
  },
390
396
 
391
397
  /* Word-level selectors (詞級選擇器)
392
398
  */
393
399
  group: {
394
400
  biaodian: [
395
- new RegExp( '((' + rBd + '){2,})', 'g' ),
396
- new RegExp( '(' + rBdLiga + rBdOpen + ')', 'g' )
401
+ new RegExp( '((' + rBD + '){2,})', 'g' ),
402
+ new RegExp( '(' + rBDLiga + rBDOpen + ')', 'g' )
397
403
  ],
398
404
  punct: null,
399
405
  hanzi: new RegExp( '(' + rHan + ')+', 'g' ),
@@ -405,11 +411,11 @@ var TYPESET = (function() {
405
411
  /* Punctuation Rules (禁則)
406
412
  */
407
413
  jinze: {
408
- hanging: new RegExp( '(' + rBdClose + '*|[…⋯]*)([、,。.])(?!' + rBdEnd + ')', 'ig' ),
409
- touwei: new RegExp( '(' + rBdOpen + '+)(' + rChar + ')(' + rBdEnd + '+)', 'ig' ),
410
- tou: new RegExp( '(' + rBdOpen + '+)(' + rChar + ')', 'ig' ),
411
- wei: new RegExp( '(' + rChar + ')(' + rBdEnd + '+)', 'ig' ),
412
- middle: new RegExp( '(' + rChar + ')(' + rBdMid + ')(' + rChar + ')', 'ig' )
414
+ hanging: new RegExp( rWhite + '*([、,。.])(?!' + rBDEnd + ')', 'ig' ),
415
+ touwei: new RegExp( '(' + rBDOpen + '+)(' + rChar + ')(' + rBDEnd + '+)', 'ig' ),
416
+ tou: new RegExp( '(' + rBDOpen + '+)(' + rChar + ')', 'ig' ),
417
+ wei: new RegExp( '(' + rChar + ')(' + rBDEnd + '+)', 'ig' ),
418
+ middle: new RegExp( '(' + rChar + ')(' + rBDMid + ')(' + rChar + ')', 'ig' )
413
419
  },
414
420
 
415
421
  zhuyin: {
@@ -507,71 +513,119 @@ Han.TYPESET.char.greek = Han.TYPESET.char.ellinika
507
513
  Han.TYPESET.char.cyrillic = Han.TYPESET.char.kirillica
508
514
  Han.TYPESET.char.hangul = Han.TYPESET.char.eonmun
509
515
 
516
+ Han.TYPESET.group.hangul = Han.TYPESET.group.eonmun
517
+ Han.TYPESET.group.cjk = Han.TYPESET.group.hanzi
518
+
510
519
  var $ = {
511
- // Simplified query selectors which return the node list
512
- // in an array
513
- id: function( selector, context ) {
514
- return ( context || document ).getElementById( selector )
520
+ /**
521
+ * Query selectors which return arrays of the resulted
522
+ * node lists.
523
+ */
524
+ id: function( selector, $context ) {
525
+ return ( $context || document ).getElementById( selector )
515
526
  },
516
527
 
517
- tag: function( selector, context ) {
528
+ tag: function( selector, $context ) {
518
529
  return this.makeArray(
519
- ( context || document ).getElementsByTagName( selector )
530
+ ( $context || document ).getElementsByTagName( selector )
520
531
  )
521
532
  },
522
533
 
523
- qsa: function( selector, context ) {
534
+ qs: function( selector, $context ) {
535
+ return ( $context || document ).querySelector( selector )
536
+ },
537
+
538
+ qsa: function( selector, $context ) {
524
539
  return this.makeArray(
525
- ( context || document ).querySelectorAll( selector )
540
+ ( $context || document ).querySelectorAll( selector )
526
541
  )
527
542
  },
528
543
 
529
- // Create a document fragment, a text node with text
530
- // or an element with/without classes
531
- create: function( elem, clazz ) {
532
- var elem = '!' === elem ?
533
- document.createDocumentFragment() :
534
- '' === elem ?
535
- document.createTextNode( clazz || '' ) :
536
- document.createElement( elem )
544
+ parent: function( $node, selector ) {
545
+ return selector
546
+ ? (function() {
547
+ if ( typeof $.matches !== 'function' ) return
548
+
549
+ while (!$.matches( $node, selector )) {
550
+ if (
551
+ !$node ||
552
+ $node === document.documentElement
553
+ ) {
554
+ $node = undefined
555
+ break
556
+ }
557
+ $node = $node.parentNode
558
+ }
559
+ return $node
560
+ })()
561
+ : $node
562
+ ? $node.parentNode : undefined
563
+ },
564
+
565
+ /**
566
+ * Create a document fragment, a text node with text
567
+ * or an element with/without classes.
568
+ */
569
+ create: function( name, clazz ) {
570
+ var $elmt = '!' === name
571
+ ? document.createDocumentFragment()
572
+ : '' === name
573
+ ? document.createTextNode( clazz || '' )
574
+ : document.createElement( name )
537
575
 
538
576
  try {
539
577
  if ( clazz ) {
540
- elem.className = clazz
578
+ $elmt.className = clazz
541
579
  }
542
580
  } catch (e) {}
543
581
 
544
- return elem
582
+ return $elmt
545
583
  },
546
584
 
547
- // Clone a node (text, element or fragment) deeply or
548
- // childlessly
549
- clone: function( node, deep ) {
550
- return node.cloneNode( typeof deep === 'boolean' ? deep : true )
585
+ /**
586
+ * Clone a DOM node (text, element or fragment) deeply
587
+ * or childlessly.
588
+ */
589
+ clone: function( $node, deep ) {
590
+ return $node.cloneNode(
591
+ typeof deep === 'boolean'
592
+ ? deep
593
+ : true
594
+ )
551
595
  },
552
596
 
553
- // Remove a node (text, element or fragment)
554
- remove: function( node ) {
555
- return node.parentNode.removeChild( node )
597
+ /**
598
+ * Remove a node (text, element or fragment).
599
+ */
600
+ remove: function( $node ) {
601
+ return $node.parentNode.removeChild( $node )
556
602
  },
557
603
 
558
- // Set attributes all in once with an object
604
+ /**
605
+ * Set attributes all in once with an object.
606
+ */
559
607
  setAttr: function( target, attr ) {
560
- if ( typeof attr !== 'object' ) return
608
+ if ( typeof attr !== 'object' ) return
561
609
  var len = attr.length
562
610
 
563
- // Native NamedNodeMap
564
- if ( typeof attr[ 0 ] === 'object' && 'name' in attr[ 0 ] ) {
611
+ // Native `NamedNodeMap``:
612
+ if (
613
+ typeof attr[0] === 'object' &&
614
+ 'name' in attr[0]
615
+ ) {
565
616
  for ( var i = 0; i < len; i++ ) {
566
617
  if ( attr[ i ].value !== undefined ) {
567
618
  target.setAttribute( attr[ i ].name, attr[ i ].value )
568
619
  }
569
620
  }
570
621
 
571
- // Plain object
622
+ // Plain object:
572
623
  } else {
573
624
  for ( var name in attr ) {
574
- if ( attr.hasOwnProperty( name ) && attr[ name ] !== undefined ) {
625
+ if (
626
+ attr.hasOwnProperty( name ) &&
627
+ attr[ name ] !== undefined
628
+ ) {
575
629
  target.setAttribute( name, attr[ name ] )
576
630
  }
577
631
  }
@@ -579,29 +633,47 @@ var $ = {
579
633
  return target
580
634
  },
581
635
 
582
- // Return if the current node should be ignored,
583
- // `<wbr>` or comments
584
- isIgnorable: function( node ) {
585
- return node.nodeName === 'WBR' || node.nodeType === Node.COMMENT_NODE
636
+ /**
637
+ * Indicate whether or not the given node is an
638
+ * element.
639
+ */
640
+ isElmt: function( $node ) {
641
+ return $node && $node.nodeType === Node.ELEMENT_NODE
586
642
  },
587
643
 
588
- // Convert array-like objects into real arrays
589
- // for the native prototype methods
590
- makeArray: function( obj ) {
591
- return Array.prototype.slice.call( obj )
644
+ /**
645
+ * Indicate whether or not the given node should
646
+ * be ignored (`<wbr>` or comments).
647
+ */
648
+ isIgnorable: function( $node ) {
649
+ if ( !$node ) return false
650
+
651
+ return (
652
+ $node.nodeName === 'WBR' ||
653
+ $node.nodeType === Node.COMMENT_NODE
654
+ )
655
+ },
656
+
657
+ /**
658
+ * Convert array-like objects into real arrays.
659
+ */
660
+ makeArray: function( object ) {
661
+ return Array.prototype.slice.call( object )
592
662
  },
593
663
 
594
- // Extend target with an object
664
+ /**
665
+ * Extend target with an object.
666
+ */
595
667
  extend: function( target, object ) {
596
- var isExtensible = typeof target === 'object' ||
597
- typeof target === 'function' ||
668
+ if ((
669
+ typeof target === 'object' ||
670
+ typeof target === 'function' ) &&
598
671
  typeof object === 'object'
599
-
600
- if ( !isExtensible ) return
601
-
602
- for ( var name in object ) {
603
- if ( object.hasOwnProperty( name )) {
604
- target[ name ] = object[ name ]
672
+ ) {
673
+ for ( var name in object ) {
674
+ if (object.hasOwnProperty( name )) {
675
+ target[ name ] = object[ name ]
676
+ }
605
677
  }
606
678
  }
607
679
  return target
@@ -628,9 +700,9 @@ var document = global.document || undefined
628
700
  function matches( node, selector, bypassNodeType39 ) {
629
701
  var Efn = Element.prototype
630
702
  var matches = Efn.matches || Efn.mozMatchesSelector || Efn.msMatchesSelector || Efn.webkitMatchesSelector
631
-
703
+
632
704
  if ( node instanceof Element ) {
633
- return matches.call( node, selector )
705
+ return matches.call( node, selector )
634
706
  } else if ( bypassNodeType39 ) {
635
707
  if ( /^[39]$/.test( node.nodeType )) return true
636
708
  }
@@ -672,7 +744,7 @@ Fibre.fn = Fibre.prototype = {
672
744
  }
673
745
 
674
746
  if ( !context ) {
675
- throw new Error( 'A context is required for Fibre to initialise.' )
747
+ throw new Error( 'A context is required for Fibre to initialise.' )
676
748
  } else if ( context instanceof Node ) {
677
749
  if ( context instanceof Document ) this.context = context.body || context
678
750
  else this.context = context
@@ -748,23 +820,23 @@ Fibre.fn = Fibre.prototype = {
748
820
  replace: function( regexp, newSubStr ) {
749
821
  var it = this
750
822
  it.finder.push(Finder( it.context, {
751
- find: regexp,
823
+ find: regexp,
752
824
  replace: newSubStr,
753
825
  filterElements: function( currentNode ) {
754
826
  return it.filterFn( currentNode )
755
- },
827
+ },
756
828
  forceContext: function( currentNode ) {
757
829
  return it.boundaryFn( currentNode )
758
830
  },
759
831
  portionMode: it.portionMode
760
832
  }))
761
- return it
833
+ return it
762
834
  },
763
835
 
764
836
  wrap: function( regexp, strElemName ) {
765
837
  var it = this
766
838
  it.finder.push(Finder( it.context, {
767
- find: regexp,
839
+ find: regexp,
768
840
  wrap: strElemName,
769
841
  filterElements: function( currentNode ) {
770
842
  return it.filterFn( currentNode )
@@ -778,7 +850,7 @@ Fibre.fn = Fibre.prototype = {
778
850
  },
779
851
 
780
852
  revert: function( level ) {
781
- var max = this.finder.length
853
+ var max = this.finder.length
782
854
  var level = Number( level ) || ( level === 0 ? Number(0) :
783
855
  ( level === 'all' ? max : 1 ))
784
856
 
@@ -862,7 +934,7 @@ return Fibre
862
934
  m.index += m[0].indexOf(cg)
863
935
  m[0] = cg
864
936
  }
865
-
937
+
866
938
  m.endIndex = m.index + m[0].length
867
939
  m.startIndex = m.index
868
940
  m.index = mi
@@ -876,9 +948,9 @@ return Fibre
876
948
  return true
877
949
  }
878
950
 
879
- /**
951
+ /**
880
952
  * findAndReplaceDOMText
881
- *
953
+ *
882
954
  * Locates matches and replaces with replacementNode
883
955
  *
884
956
  * @param {Node} node Element or Text node to search within
@@ -1011,7 +1083,7 @@ return Fibre
1011
1083
  if (!match[0]) {
1012
1084
  throw new Error('findAndReplaceDOMText cannot handle zero-length matches')
1013
1085
  }
1014
-
1086
+
1015
1087
  match.endIndex = characterOffset + match.index + match[0].length
1016
1088
  match.startIndex = characterOffset + match.index
1017
1089
  match.index = matchIndex
@@ -1072,10 +1144,10 @@ return Fibre
1072
1144
  } while (node = node.nextSibling)
1073
1145
  return txt
1074
1146
  }
1075
-
1147
+
1076
1148
  },
1077
1149
 
1078
- /**
1150
+ /**
1079
1151
  * Steps through the target node, looking for matches, and
1080
1152
  * calling replaceFn when a match is found.
1081
1153
  */
@@ -1141,7 +1213,7 @@ return Fibre
1141
1213
 
1142
1214
  curNode = this.replaceMatch(match, startPortion, innerPortions, endPortion)
1143
1215
  // processMatches has to return the node that replaced the endNode
1144
- // and then we step back so we can continue from the end of the
1216
+ // and then we step back so we can continue from the end of the
1145
1217
  // match:
1146
1218
 
1147
1219
  atIndex -= (endPortion.node.data.length - endPortion.endIndexInNode)
@@ -1363,37 +1435,82 @@ return Fibre
1363
1435
 
1364
1436
  );
1365
1437
 
1366
- function createBdGroup( portion, match ) {
1367
- var elem = $.create( 'h-char-group', 'biaodian cjk' )
1438
+ var isNodeNormalizeNormal = (function() {
1439
+ //// Disabled `Node.normalize()` for temp due to
1440
+ //// issue below in IE11.
1441
+ //// See: http://stackoverflow.com/questions/22337498/why-does-ie11-handle-node-normalize-incorrectly-for-the-minus-symbol
1442
+ var div = $.create( 'div' )
1368
1443
 
1369
- if ( portion.index === 0 && portion.isEnd ) {
1370
- elem.innerHTML = match[0]
1371
- } else {
1372
- elem.innerHTML = portion.text
1373
- elem.classList.add( 'portion' )
1444
+ div.appendChild($.create( '', '0-' ))
1445
+ div.appendChild($.create( '', '2' ))
1446
+ div.normalize()
1374
1447
 
1375
- if ( portion.index === 0 ) {
1376
- elem.classList.add( 'isFirst' )
1377
- } else if ( portion.isEnd ) {
1378
- elem.classList.add( 'isEnd' )
1379
- }
1380
- }
1381
- return elem
1448
+ return div.firstChild.length !== 2
1449
+ })()
1450
+
1451
+ function getFuncOrElmt( obj ) {
1452
+ return (
1453
+ typeof obj === 'function' ||
1454
+ obj instanceof Element
1455
+ )
1456
+ ? obj
1457
+ : undefined
1382
1458
  }
1383
1459
 
1384
- function createBdChar( char ) {
1385
- var div = $.create( 'div' )
1460
+ function createBDGroup( portion ) {
1461
+ var clazz = portion.index === 0 && portion.isEnd
1462
+ ? 'biaodian cjk'
1463
+ : 'biaodian cjk portion ' + (
1464
+ portion.index === 0
1465
+ ? 'is-first'
1466
+ : portion.isEnd
1467
+ ? 'is-end'
1468
+ : 'is-inner'
1469
+ )
1470
+
1471
+ var $elmt = $.create( 'h-char-group', clazz )
1472
+ $elmt.innerHTML = portion.text
1473
+ return $elmt
1474
+ }
1475
+
1476
+ function createBDChar( char ) {
1477
+ var div = $.create( 'div' )
1386
1478
  var unicode = char.charCodeAt( 0 ).toString( 16 )
1387
- var clazz = 'biaodian cjk ' + ( char.match( TYPESET.char.biaodian.open ) ? 'bd-open' :
1388
- char.match( TYPESET.char.biaodian.close ) ? 'bd-close bd-end' :
1389
- char.match( TYPESET.char.biaodian.end ) ? 'bd-end' :
1390
- char.match( new RegExp( '(' + UNICODE.biaodian.liga + ')' )) ? 'bd-liga' : '' )
1391
1479
 
1392
- div.innerHTML = '<h-char unicode="' + unicode + '" class="' + clazz + '">' + char + '</h-char>'
1480
+ div.innerHTML = (
1481
+ '<h-char unicode="' + unicode +
1482
+ '" class="biaodian cjk ' + getBDType( char ) +
1483
+ '">' + char + '</h-char>'
1484
+ )
1393
1485
  return div.firstChild
1394
1486
  }
1395
1487
 
1488
+ function getBDType( char ) {
1489
+ return char.match( TYPESET.char.biaodian.open )
1490
+ ? 'bd-open'
1491
+ : char.match( TYPESET.char.biaodian.close )
1492
+ ? 'bd-close bd-end'
1493
+ : char.match( TYPESET.char.biaodian.end )
1494
+ ? (
1495
+ /(?:\u3001|\u3002|\uff0c)/i.test( char )
1496
+ ? 'bd-end bd-cop'
1497
+ : 'bd-end'
1498
+ )
1499
+ : char.match(new RegExp( UNICODE.biaodian.liga ))
1500
+ ? 'bd-liga'
1501
+ : char.match(new RegExp( UNICODE.biaodian.middle ))
1502
+ ? 'bd-middle'
1503
+ : ''
1504
+ }
1505
+
1396
1506
  $.extend( Fibre.fn, {
1507
+ normalize: function() {
1508
+ if ( isNodeNormalizeNormal ) {
1509
+ this.context.normalize()
1510
+ }
1511
+ return this
1512
+ },
1513
+
1397
1514
  // Force punctuation & biaodian typesetting rules to be applied.
1398
1515
  jinzify: function( selector ) {
1399
1516
  return (
@@ -1449,33 +1566,34 @@ $.extend( Fibre.fn, {
1449
1566
  western: false // Includes Latin, Greek and Cyrillic
1450
1567
  }, option || {})
1451
1568
 
1452
- this.avoid( 'h-hangable, h-char-group, h-word' )
1569
+ this.avoid( 'h-word, h-char-group' )
1453
1570
 
1454
1571
  if ( option.biaodian ) {
1455
1572
  this.replace(
1456
- TYPESET.group.biaodian[ 0 ], createBdGroup
1573
+ TYPESET.group.biaodian[0], createBDGroup
1457
1574
  ).replace(
1458
- TYPESET.group.biaodian[ 1 ], createBdGroup
1575
+ TYPESET.group.biaodian[1], createBDGroup
1459
1576
  )
1460
1577
  }
1578
+
1461
1579
  if ( option.hanzi || option.cjk ) {
1462
1580
  this.wrap(
1463
- TYPESET.group.hanzi, $.clone( $.create( 'h-char-group', 'hanzi cjk' ))
1581
+ TYPESET.group.hanzi, $.clone($.create( 'h-char-group', 'hanzi cjk' ))
1464
1582
  )
1465
1583
  }
1466
1584
  if ( option.western ) {
1467
1585
  this.wrap(
1468
- TYPESET.group.western, $.clone( $.create( 'h-word', 'western' ))
1586
+ TYPESET.group.western, $.clone($.create( 'h-word', 'western' ))
1469
1587
  )
1470
1588
  }
1471
1589
  if ( option.kana ) {
1472
1590
  this.wrap(
1473
- TYPESET.group.kana, $.clone( $.create( 'h-char-group', 'kana' ))
1591
+ TYPESET.group.kana, $.clone($.create( 'h-char-group', 'kana' ))
1474
1592
  )
1475
1593
  }
1476
1594
  if ( option.eonmun || option.hangul ) {
1477
1595
  this.wrap(
1478
- TYPESET.group.eonmun, $.clone( $.create( 'h-word', 'eonmun hangul' ))
1596
+ TYPESET.group.eonmun, $.clone($.create( 'h-word', 'eonmun hangul' ))
1479
1597
  )
1480
1598
  }
1481
1599
 
@@ -1485,6 +1603,7 @@ $.extend( Fibre.fn, {
1485
1603
 
1486
1604
  charify: function( option ) {
1487
1605
  var option = $.extend({
1606
+ avoid: true,
1488
1607
  biaodian: false,
1489
1608
  punct: false,
1490
1609
  hanzi: false, // Includes Kana
@@ -1495,50 +1614,77 @@ $.extend( Fibre.fn, {
1495
1614
  eonmun: false
1496
1615
  }, option || {})
1497
1616
 
1498
- this.avoid( 'h-char' )
1617
+ if ( option.avoid ) {
1618
+ this.avoid( 'h-char' )
1619
+ }
1499
1620
 
1500
1621
  if ( option.biaodian ) {
1501
1622
  this.replace(
1502
1623
  TYPESET.char.biaodian.all,
1503
- function( portion, match ) { return createBdChar( match[0] ) }
1624
+ getFuncOrElmt( option.biaodian )
1625
+ ||
1626
+ function( portion ) { return createBDChar( portion.text ) }
1504
1627
  ).replace(
1505
1628
  TYPESET.char.biaodian.liga,
1506
- function( portion, match ) { return createBdChar( match[0] ) }
1629
+ getFuncOrElmt( option.biaodian )
1630
+ ||
1631
+ function( portion ) { return createBDChar( portion.text ) }
1507
1632
  )
1508
1633
  }
1509
1634
  if ( option.hanzi || option.cjk ) {
1510
1635
  this.wrap(
1511
- TYPESET.char.hanzi, $.clone( $.create( 'h-char', 'hanzi cjk' ))
1636
+ TYPESET.char.hanzi,
1637
+ getFuncOrElmt( option.hanzi || option.cjk )
1638
+ ||
1639
+ $.clone($.create( 'h-char', 'hanzi cjk' ))
1512
1640
  )
1513
1641
  }
1514
1642
  if ( option.punct ) {
1515
1643
  this.wrap(
1516
- TYPESET.char.punct.all, $.clone( $.create( 'h-char', 'punct' ))
1644
+ TYPESET.char.punct.all,
1645
+ getFuncOrElmt( option.punct )
1646
+ ||
1647
+ $.clone($.create( 'h-char', 'punct' ))
1517
1648
  )
1518
1649
  }
1519
1650
  if ( option.latin ) {
1520
1651
  this.wrap(
1521
- TYPESET.char.latin, $.clone( $.create( 'h-char', 'alphabet latin' ))
1652
+ TYPESET.char.latin,
1653
+ getFuncOrElmt( option.latin )
1654
+ ||
1655
+ $.clone($.create( 'h-char', 'alphabet latin' ))
1522
1656
  )
1523
1657
  }
1524
1658
  if ( option.ellinika || option.greek ) {
1525
1659
  this.wrap(
1526
- TYPESET.char.ellinika, $.clone( $.create( 'h-char', 'alphabet ellinika greek' ))
1660
+ TYPESET.char.ellinika,
1661
+ getFuncOrElmt( option.ellinika || option.greek )
1662
+ ||
1663
+ $.clone($.create( 'h-char', 'alphabet ellinika greek' ))
1527
1664
  )
1528
1665
  }
1529
1666
  if ( option.kirillica || option.cyrillic ) {
1530
1667
  this.wrap(
1531
- TYPESET.char.kirillica, $.clone( $.create( 'h-char', 'alphabet kirillica cyrillic' ))
1668
+ TYPESET.char.kirillica,
1669
+ getFuncOrElmt( option.kirillica || option.cyrillic )
1670
+ ||
1671
+ $.clone($.create( 'h-char', 'alphabet kirillica cyrillic' ))
1532
1672
  )
1533
1673
  }
1534
1674
  if ( option.kana ) {
1535
1675
  this.wrap(
1536
- TYPESET.char.kana, $.clone( $.create( 'h-char', 'kana' ))
1676
+ TYPESET.char.kana,
1677
+ getFuncOrElmt( option.kana )
1678
+ ||
1679
+ $.clone($.create( 'h-char', 'kana' ))
1537
1680
  )
1538
1681
  }
1539
1682
  if ( option.eonmun || option.hangul ) {
1540
1683
  this.wrap(
1541
- TYPESET.char.eonmun, $.clone( $.create( 'h-char', 'eonmun hangul' ))
1684
+ TYPESET.char.eonmun,
1685
+ getFuncOrElmt( option.eonmun || option.hangul )
1686
+ ||
1687
+ $.clone($.create( 'h-char', 'eonmun hangul' ))
1542
1688
  )
1543
1689
  }
1544
1690
 
@@ -1547,7 +1693,14 @@ $.extend( Fibre.fn, {
1547
1693
  }
1548
1694
  })
1549
1695
 
1550
- Han.find = Fibre
1696
+ $.extend( Han, {
1697
+ isNodeNormalizeNormal: isNodeNormalizeNormal,
1698
+ find: Fibre,
1699
+ createBDGroup: createBDGroup,
1700
+ createBDChar: createBDChar
1701
+ })
1702
+
1703
+ $.matches = Han.find.matches
1551
1704
 
1552
1705
  void [
1553
1706
  'setMode',
@@ -2021,7 +2174,7 @@ function createNormalRu( $rb, $rt, attr ) {
2021
2174
  if ( Array.isArray( $rb )) {
2022
2175
  $ru.innerHTML = $rb.map(function( rb ) {
2023
2176
  if ( typeof rb === 'undefined' ) return ''
2024
- return rb.outerHTML
2177
+ return rb.outerHTML
2025
2178
  }).join('') + $rt.outerHTML
2026
2179
  } else {
2027
2180
  $ru.appendChild( $.clone( $rb ))
@@ -2131,32 +2284,34 @@ $.extend( Locale, {
2131
2284
  this.renderEm( context )
2132
2285
  },
2133
2286
 
2134
- // Traverse target elements (those with text-decoration-
2135
- // line) to see if we should address spacing in
2136
- // between for semantic presentation.
2287
+ // Traverse all target elements and address
2288
+ // presentational corrections if any two of
2289
+ // them are adjacent to each other.
2137
2290
  renderDecoLine: function( context, target ) {
2138
- var target = target || 'u, ins'
2139
- var $target = $.qsa( target, context )
2140
- var rTarget = new RegExp( '^(' + target.replace(/\,\s?/g, '|') + ')$', 'ig' )
2141
-
2142
- $target
2143
- .forEach(function( elem ) {
2144
- var next
2145
-
2146
- // Ignore all `<wbr>` and comments in between
2147
- do {
2148
- next = ( next || elem ).nextSibling
2149
- if ( !next ) return
2150
- } while ( $.isIgnorable( next ))
2151
-
2152
- if ( next.nodeName.match( rTarget )) {
2153
- next.classList.add( 'adjacent' )
2154
- }
2155
- })
2291
+ var $$target = $.qsa( target || 'u, ins', context )
2292
+ var i = $$target.length
2293
+
2294
+ traverse: while ( i-- ) {
2295
+ var $this = $$target[ i ]
2296
+ var $prev = null
2297
+
2298
+ // Ignore all `<wbr>` and comments in between,
2299
+ // and add class `.adjacent` once two targets
2300
+ // are next to each other.
2301
+ ignore: do {
2302
+ $prev = ( $prev || $this ).previousSibling
2303
+
2304
+ if ( !$prev ) {
2305
+ continue traverse
2306
+ } else if ( $$target[ i-1 ] === $prev ) {
2307
+ $this.classList.add( 'adjacent' )
2308
+ }
2309
+ } while ( $.isIgnorable( $prev ))
2310
+ }
2156
2311
  },
2157
2312
 
2158
- // Traverse target elements to render Hanzi emphasis marks
2159
- // and skip that in punctuation
2313
+ // Traverse all target elements to render
2314
+ // emphasis marks.
2160
2315
  renderEm: function( context, target ) {
2161
2316
  var method = target ? 'qsa' : 'tag'
2162
2317
  var target = target || 'em'
@@ -2168,13 +2323,13 @@ $.extend( Locale, {
2168
2323
 
2169
2324
  if ( Locale.support.textemphasis ) {
2170
2325
  $elem
2171
- .avoid( 'rt, h-char, h-char-group' )
2326
+ .avoid( 'rt, h-char' )
2172
2327
  .charify({ biaodian: true, punct: true })
2173
2328
  } else {
2174
2329
  $elem
2175
2330
  .avoid( 'rt, h-char, h-char-group' )
2176
2331
  .jinzify()
2177
- .groupify({ western: true, biaodian: true })
2332
+ .groupify({ western: true })
2178
2333
  .charify({
2179
2334
  hanzi: true,
2180
2335
  biaodian: true,
@@ -2229,101 +2384,160 @@ $.extend( Han.support, {
2229
2384
  // 'fangsong-gb': Han.detectFont( '"Han Fangsong GB"' )
2230
2385
  })
2231
2386
 
2232
- var QUERY_HWS_AS_FIRST_CHILD = '* > h-hws:first-child, * > wbr:first-child + h-hws, wbr:first-child + wbr + h-hws'
2387
+ Han.correctBiaodian = function( context ) {
2388
+ var context = context || document
2389
+ var finder = Han.find( context )
2233
2390
 
2234
- //// Disabled `Node.normalize()` for temp due to
2235
- //// issue below in IE11.
2236
- //// See: http://stackoverflow.com/questions/22337498/why-does-ie11-handle-node-normalize-incorrectly-for-the-minus-symbol
2237
- var isNodeNormalizeNormal = (function() {
2238
- var div = $.create( 'div' )
2391
+ finder
2392
+ .avoid( 'h-char' )
2393
+ .replace( /([‘“])/g, function( portion ) {
2394
+ var $char = Han.createBDChar( portion.text )
2395
+ $char.classList.add( 'bd-open', 'punct' )
2396
+ return $char
2397
+ })
2398
+ .replace( /([’”])/g, function( portion ) {
2399
+ var $char = Han.createBDChar( portion.text )
2400
+ $char.classList.add( 'bd-close', 'bd-end', 'punct' )
2401
+ return $char
2402
+ })
2239
2403
 
2240
- div.appendChild( $.create( '', '0-' ))
2241
- div.appendChild( $.create( '', '2' ))
2242
- div.normalize()
2404
+ return Han.support.unicoderange
2405
+ ? finder
2406
+ : finder.charify({ biaodian: true })
2407
+ }
2243
2408
 
2244
- return div.firstChild.length !== 2
2245
- })()
2409
+ Han.correctBasicBD = Han.correctBiaodian
2410
+ Han.correctBD = Han.correctBiaodian
2246
2411
 
2247
- var hws = $.create( 'h-hws' )
2248
- hws.setAttribute( 'hidden', '' )
2249
- hws.innerHTML = ' '
2412
+ $.extend( Han.fn, {
2413
+ biaodian: null,
2250
2414
 
2251
- $.extend( Han, {
2252
- isNodeNormalizeNormal: isNodeNormalizeNormal,
2415
+ correctBiaodian: function() {
2416
+ this.biaodian = Han.correctBiaodian( this.context )
2417
+ return this
2418
+ },
2253
2419
 
2254
- renderHWS: function( context, strict ) {
2255
- var context = context || document
2256
- var mode = strict ? 'strict' : 'base'
2257
- var finder = Han.find( context )
2420
+ revertCorrectedBiaodian: function() {
2421
+ try {
2422
+ this.biaodian.revert( 'all' )
2423
+ } catch (e) {}
2424
+ return this
2425
+ }
2426
+ })
2258
2427
 
2259
- // Elements to be filtered according to the
2260
- // HWS rendering mode
2261
- if ( strict ) {
2262
- finder.avoid( 'textarea, code, kbd, samp, pre' )
2263
- } else {
2264
- finder.avoid( 'textarea' )
2428
+ // Legacy support (deprecated):
2429
+ Han.fn.correctBasicBD = Han.fn.correctBiaodian
2430
+ Han.fn.revertBasicBD = Han.fn.revertCorrectedBiaodian
2431
+
2432
+ var hws = '<<hws>>'
2433
+
2434
+ var $hws = $.create( 'h-hws' )
2435
+ $hws.setAttribute( 'hidden', '' )
2436
+ $hws.innerHTML = ' '
2437
+
2438
+ function sharingSameParent( $a, $b ) {
2439
+ return $a && $b && $a.parentNode === $b.parentNode
2440
+ }
2441
+
2442
+ function properlyPlaceHWSBehind( $node, text ) {
2443
+ var $elmt = $node
2444
+ var text = text || ''
2445
+
2446
+ if (
2447
+ $.isElmt( $node.nextSibling ) ||
2448
+ sharingSameParent( $node, $node.nextSibling )
2449
+ ) {
2450
+ return text + hws
2451
+ } else {
2452
+ // One of the parental elements of the current text
2453
+ // node would definitely have a next sibling, since
2454
+ // it is of the first portion and not `isEnd`.
2455
+ while ( !$elmt.nextSibling ) {
2456
+ $elmt = $elmt.parentNode
2457
+ }
2458
+ if ( $node !== $elmt ) {
2459
+ $elmt.insertAdjacentHTML( 'afterEnd', '<h-hws hidden> </h-hws>' )
2265
2460
  }
2461
+ }
2462
+ return text
2463
+ }
2266
2464
 
2267
- finder
2268
- .replace( Han.TYPESET.hws[ mode ][0], '$1<hws/>$2' )
2269
- .replace( Han.TYPESET.hws[ mode ][1], '$1<hws/>$2' )
2465
+ function firstStepLabel( portion, mat ) {
2466
+ return portion.isEnd && portion.index === 0
2467
+ ? mat[1] + hws + mat[2]
2468
+ : portion.index === 0
2469
+ ? properlyPlaceHWSBehind( portion.node, portion.text )
2470
+ : portion.text
2471
+ }
2270
2472
 
2271
- // Deal with [' 字'], [" 字"] => ['字'], ["字"]
2272
- .replace( /(['"]+)<hws\/>(.+?)<hws\/>\1/ig, '$1$2$1' )
2473
+ function real$hwsElmt( portion ) {
2474
+ return portion.index === 0
2475
+ ? $.clone( $hws )
2476
+ : ''
2477
+ }
2273
2478
 
2274
- // Remove all `<hws/>` pre/post [“字”] and [‘字’]
2275
- // See: https://github.com/ethantw/Han/issues/59
2276
- .replace( /<hws\/>([‘“]+)/ig, '$1' )
2277
- .replace( /([’”]+)<hws\/>/ig, '$1' )
2479
+ var last$hwsIdx
2278
2480
 
2279
- // Convert text nodes `<hws/>` into real element nodes
2280
- .replace( '<hws/>', function() {
2281
- return $.clone( hws )
2282
- })
2481
+ function apostrophe( portion ) {
2482
+ var $elmt = portion.node.parentNode
2283
2483
 
2284
- // Deal with:
2285
- // `漢<u><hws/>zi</u>` => `漢<hws/><u>zi</u>`
2286
- $
2287
- .qsa( QUERY_HWS_AS_FIRST_CHILD, context )
2288
- .forEach(function( firstChild ) {
2289
- var parent = firstChild.parentNode
2290
- var target = parent.firstChild
2291
-
2292
- // Skip all `<wbr>` and comments
2293
- while ( $.isIgnorable( target )) {
2294
- target = target.nextSibling
2295
-
2296
- if ( !target ) return
2297
- }
2484
+ if ( portion.index === 0 ) {
2485
+ last$hwsIdx = portion.endIndexInNode-2
2486
+ }
2298
2487
 
2299
- // The ‘first-child’ of DOM is different from
2300
- // the ones of QSA, could be either an element
2301
- // or a text fragment, but the latter one is
2302
- // not what we want. We don't want comments,
2303
- // either.
2304
- while ( target.nodeName === 'H-HWS' ) {
2305
- $.remove( target, parent )
2488
+ if (
2489
+ $elmt.nodeName.toLowerCase() === 'h-hws' && (
2490
+ portion.index === 1 || portion.indexInMatch === last$hwsIdx
2491
+ )) {
2492
+ $elmt.classList.add( 'quote-inner' )
2493
+ }
2494
+ return portion.text
2495
+ }
2306
2496
 
2307
- target = parent.parentNode.insertBefore( $.clone( hws ), parent )
2308
- parent = parent.parentNode
2497
+ function curveQuote( portion ) {
2498
+ var $elmt = portion.node.parentNode
2309
2499
 
2310
- if ( isNodeNormalizeNormal ) {
2311
- parent.normalize()
2312
- }
2500
+ if ( $elmt.nodeName.toLowerCase() === 'h-hws' ) {
2501
+ $elmt.classList.add( 'quote-outer' )
2502
+ }
2503
+ return portion.text
2504
+ }
2313
2505
 
2314
- // This is for extreme circumstances, i.e.,
2315
- // `漢<a><b><c><h-hws/>zi</c></b></a>` =>
2316
- // `漢<h-hws/><a><b><c>zi</c></b></a>`
2317
- if ( target !== parent.firstChild ) {
2318
- break
2319
- }
2320
- }
2321
- })
2506
+ $.extend( Han, {
2507
+ renderHWS: function( context, strict ) {
2508
+ // Elements to be filtered according to the
2509
+ // HWS rendering mode.
2510
+ var AVOID = strict
2511
+ ? 'textarea, code, kbd, samp, pre'
2512
+ : 'textarea'
2322
2513
 
2323
- // Normalise nodes we messed up with
2324
- if ( isNodeNormalizeNormal ) {
2325
- context.normalize()
2326
- }
2514
+ var mode = strict ? 'strict' : 'base'
2515
+ var context = context || document
2516
+ var finder = Han.find( context )
2517
+
2518
+ finder
2519
+ .avoid( AVOID )
2520
+
2521
+ // Basic situations:
2522
+ // - 字a => 字<hws/>a
2523
+ // - A字 => A<hws/>字
2524
+ .replace( Han.TYPESET.hws[ mode ][0], firstStepLabel )
2525
+ .replace( Han.TYPESET.hws[ mode ][1], firstStepLabel )
2526
+
2527
+ // Convert text nodes `<hws/>` into real element nodes:
2528
+ .replace( new RegExp( '(' + hws + ')+', 'g' ), real$hwsElmt )
2529
+
2530
+ // Deal with:
2531
+ // - '<hws/>字<hws/>' => '字'
2532
+ // - "<hws/>字<hws/>" => "字"
2533
+ .replace( /([\'"])\s(.+?)\s\1/g, apostrophe )
2534
+
2535
+ // Deal with:
2536
+ // - <hws/>“字”<hws/>
2537
+ // - <hws/>‘字’<hws/>
2538
+ .replace( /\s[‘“]/g, curveQuote )
2539
+ .replace( /[’”]\s/g, curveQuote )
2540
+ .normalize()
2327
2541
 
2328
2542
  // Return the finder instance for future usage
2329
2543
  return finder
@@ -2331,23 +2545,27 @@ $.extend( Han, {
2331
2545
  })
2332
2546
 
2333
2547
  $.extend( Han.fn, {
2334
- HWS: null,
2335
-
2336
2548
  renderHWS: function( strict ) {
2337
2549
  Han.renderHWS( this.context, strict )
2338
-
2339
- this.HWS = $.tag( 'h-hws', this.context )
2340
2550
  return this
2341
2551
  },
2342
2552
 
2343
2553
  revertHWS: function() {
2344
- this.HWS.forEach(function( hws ) {
2554
+ $.tag( 'h-hws', this.context )
2555
+ .forEach(function( hws ) {
2345
2556
  $.remove( hws )
2346
2557
  })
2558
+ this.HWS = []
2347
2559
  return this
2348
2560
  }
2349
2561
  })
2350
2562
 
2563
+ var HANGABLE_CLASS = 'bd-hangable'
2564
+ var HANGABLE_AVOID = 'h-char.bd-hangable'
2565
+ var HANGABLE_CS_HTML = '<h-cs hidden class="jinze-outer hangable-outer"> </h-cs>'
2566
+
2567
+ var matches = Han.find.matches
2568
+
2351
2569
  function detectSpaceFont() {
2352
2570
  var div = $.create( 'div' )
2353
2571
  var ret
@@ -2355,144 +2573,253 @@ function detectSpaceFont() {
2355
2573
  div.innerHTML = '<span>a b</span><span style="font-family: \'Han Space\'">a b</span>'
2356
2574
  body.appendChild( div )
2357
2575
  ret = div.firstChild.offsetWidth !== div.lastChild.offsetWidth
2358
- $.remove( div, body )
2576
+ $.remove( div )
2359
2577
  return ret
2360
2578
  }
2361
2579
 
2580
+ function insertHangableCS( $jinze ) {
2581
+ var $cs = $jinze.nextSibling
2582
+
2583
+ if ( $cs && matches( $cs, 'h-cs.jinze-outer' )) {
2584
+ $cs.classList.add( 'hangable-outer' )
2585
+ } else {
2586
+ $jinze.insertAdjacentHTML(
2587
+ 'afterend',
2588
+ HANGABLE_CS_HTML
2589
+ )
2590
+ }
2591
+ }
2592
+
2593
+ Han.support['han-space'] = detectSpaceFont()
2594
+
2362
2595
  $.extend( Han, {
2363
2596
  detectSpaceFont: detectSpaceFont,
2364
- isSpaceFontLoaded: detectSpaceFont()
2365
- })
2597
+ isSpaceFontLoaded: detectSpaceFont(),
2366
2598
 
2367
- Han.support['han-space'] = detectSpaceFont()
2599
+ renderHanging: function( context ) {
2600
+ var context = context || document
2601
+ var finder = Han.find( context )
2368
2602
 
2369
- Han.renderHanging = function( context ) {
2370
- var context = context || document
2371
- var finder = Han.find( context )
2603
+ finder
2604
+ .avoid( 'textarea, code, kbd, samp, pre' )
2605
+ .avoid( HANGABLE_AVOID )
2606
+ .replace(
2607
+ TYPESET.jinze.hanging,
2608
+ function( portion ) {
2609
+ if ( /^[\x20\t\r\n\f]+$/.test( portion.text )) {
2610
+ return ''
2611
+ }
2372
2612
 
2373
- finder
2374
- .avoid( 'textarea, code, kbd, samp, pre, h-hangable' )
2375
- .replace(
2376
- TYPESET.jinze.hanging,
2377
- function( portion, match ) {
2378
- var elem = $.create( 'h-hangable' )
2379
- elem.innerHTML = match[1] + '<h-cs><h-inner hidden> </h-inner><h-char class="biaodian bd-close bd-end cjk">' + match[2] + '</h-char></h-cs>'
2380
- return portion.index === 0 ? elem : ''
2381
- }
2382
- )
2613
+ var $elmt = portion.node.parentNode
2614
+ var $jinze, $new, $bd, biaodian
2383
2615
 
2384
- return finder
2385
- }
2616
+ if ( $jinze = $.parent( $elmt, 'h-jinze' )) {
2617
+ insertHangableCS( $jinze )
2618
+ }
2386
2619
 
2387
- $.extend( Han.fn, {
2388
- hanging: null,
2620
+ biaodian = portion.text.trim()
2389
2621
 
2390
- renderHanging: function() {
2391
- var condClazz = this.condition.classList
2392
- var isSpaceFontLoaded = detectSpaceFont()
2622
+ $new = Han.createBDChar( biaodian )
2623
+ $new.innerHTML = '<h-inner>' + biaodian + '</h-inner>'
2624
+ $new.classList.add( HANGABLE_CLASS )
2625
+
2626
+ $bd = $.parent( $elmt, 'h-char.biaodian' )
2627
+
2628
+ return !$bd
2629
+ ? $new
2630
+ : (function() {
2631
+ $bd.classList.add( HANGABLE_CLASS )
2632
+
2633
+ return matches( $elmt, 'h-inner, h-inner *' )
2634
+ ? biaodian
2635
+ : $new.firstChild
2636
+ })()
2637
+ }
2638
+ )
2639
+ return finder
2640
+ }
2641
+ })
2393
2642
 
2394
- if ( isSpaceFontLoaded && condClazz.contains( 'no-han-space' )) {
2395
- condClazz.remove( 'no-han-space' )
2396
- condClazz.add( 'han-space' )
2643
+ $.extend( Han.fn, {
2644
+ renderHanging: function() {
2645
+ var classList = this.condition.classList
2646
+ Han.isSpaceFontLoaded = detectSpaceFont()
2647
+
2648
+ if (
2649
+ Han.isSpaceFontLoaded &&
2650
+ classList.contains( 'no-han-space' )
2651
+ ) {
2652
+ classList.remove( 'no-han-space' )
2653
+ classList.add( 'han-space' )
2397
2654
  }
2398
2655
 
2399
- this.hanging = Han.renderHanging( this.context )
2656
+ Han.renderHanging( this.context )
2400
2657
  return this
2401
2658
  },
2402
2659
 
2403
2660
  revertHanging: function() {
2404
- try {
2405
- this.hanging.revert( 'all' )
2406
- } catch ( e ) {}
2661
+ $.qsa(
2662
+ 'h-char.bd-hangable, h-cs.hangable-outer',
2663
+ this.context
2664
+ ).forEach(function( $elmt ) {
2665
+ var classList = $elmt.classList
2666
+ classList.remove( 'bd-hangable' )
2667
+ classList.remove( 'hangable-outer' )
2668
+ })
2407
2669
  return this
2408
2670
  }
2409
2671
  })
2410
2672
 
2411
- Han.renderJiya = function( context ) {
2412
- var context = context || document
2413
- var finder = Han.find( context )
2673
+ var JIYA_CLASS = 'bd-jiya'
2674
+ var JIYA_AVOID = 'h-char.bd-jiya'
2675
+ var CONSECUTIVE_CLASS = 'bd-consecutive'
2676
+ var JIYA_CS_HTML = '<h-cs hidden class="jinze-outer jiya-outer"> </h-cs>'
2414
2677
 
2415
- finder
2416
- .avoid( 'textarea, code, kbd, samp, pre, h-char-group' )
2417
- .replace(
2418
- // This is a safeguard against hanging rendering
2419
- new RegExp( '(' + UNICODE.biaodian.end + '+)(' + UNICODE.biaodian.open + '+)', 'g' ),
2420
- function( portion, match ) {
2421
- if ( portion.index === 0 ) return portion.isEnd ? match[0] : match[1]
2422
-
2423
- var elem = $.create( 'h-char-group', 'biaodian cjk portion' )
2424
- elem.innerHTML = match[2]
2425
- return elem
2426
- }
2427
- )
2428
- .endAvoid()
2678
+ var matches = Han.find.matches
2429
2679
 
2430
- finder
2431
- .avoid( 'textarea, code, kbd, samp, pre, h-char' )
2432
- .groupify({ biaodian: true })
2433
- .charify({ biaodian: true })
2434
-
2435
- // The reason we're doing this instead of using pseudo elements in CSS
2436
- // is because WebKit has problem rendering pseudo elements containing only
2437
- // space.
2438
- $.qsa( 'h-char.biaodian.bd-open, h-char.biaodian.bd-end', context )
2439
- .forEach(function( elem ) {
2440
- if ( Han.find.matches( elem, 'h-cs *' )) return
2441
- var html = '<h-inner>' + elem.innerHTML + '</h-inner>'
2442
- var hcs = '<h-cs hidden> </h-cs>'
2443
- var isOpen = elem.classList.contains( 'bd-open' )
2444
- elem.innerHTML = isOpen ? hcs + html : html + hcs
2445
- })
2680
+ function trimBDClass( clazz ) {
2681
+ return clazz.replace(
2682
+ /(biaodian|cjk|bd-jiya|bd-consecutive|bd-hangable)/gi, ''
2683
+ ).trim()
2684
+ }
2446
2685
 
2447
- return finder
2686
+ function charifyBiaodian( portion ) {
2687
+ var biaodian = portion.text
2688
+ var $elmt = portion.node.parentNode
2689
+ var $bd = $.parent( $elmt, 'h-char.biaodian' )
2690
+ var $new = Han.createBDChar( biaodian )
2691
+ var $jinze
2692
+
2693
+ $new.innerHTML = '<h-inner>' + biaodian + '</h-inner>'
2694
+ $new.classList.add( JIYA_CLASS )
2695
+
2696
+ if ( $jinze = $.parent( $elmt, 'h-jinze' )) {
2697
+ insertJiyaCS( $jinze )
2698
+ }
2699
+
2700
+ return !$bd
2701
+ ? $new
2702
+ : (function() {
2703
+ $bd.classList.add( JIYA_CLASS )
2704
+
2705
+ return matches( $elmt, 'h-inner, h-inner *' )
2706
+ ? biaodian
2707
+ : $new.firstChild
2708
+ })()
2448
2709
  }
2449
2710
 
2450
- $.extend( Han.fn, {
2451
- jiya: null,
2711
+ var prevBDType, $$prevCS
2452
2712
 
2453
- renderJiya: function() {
2454
- this.jiya = Han.renderJiya( this.context )
2455
- return this
2456
- },
2713
+ function locateConsecutiveBD( portion ) {
2714
+ var prev = prevBDType
2715
+ var $elmt = portion.node.parentNode
2716
+ var $bd = $.parent( $elmt, 'h-char.biaodian' )
2717
+ var $jinze = $.parent( $bd, 'h-jinze' )
2718
+ var classList
2457
2719
 
2458
- revertJiya: function() {
2459
- try {
2460
- this.jiya.revert( 'all' )
2461
- } catch ( e ) {}
2462
- return this
2720
+ classList = $bd.classList
2721
+
2722
+ if ( prev ) {
2723
+ $bd.setAttribute( 'prev', prev )
2724
+ }
2725
+
2726
+ if ( $$prevCS && classList.contains( 'bd-open' )) {
2727
+ $$prevCS.pop().setAttribute( 'next', 'bd-open' )
2463
2728
  }
2464
- })
2465
2729
 
2466
- var mdot
2730
+ $$prevCS = undefined
2467
2731
 
2468
- mdot = $.create( 'h-char', 'biaodian cjk bd-middle' )
2469
- mdot.setAttribute( 'unicode', 'b7' )
2732
+ if ( portion.isEnd ) {
2733
+ prevBDType = undefined
2734
+ classList.add( CONSECUTIVE_CLASS, 'end-portion' )
2735
+ } else {
2736
+ prevBDType = trimBDClass($bd.getAttribute( 'class' ))
2737
+ classList.add( CONSECUTIVE_CLASS )
2738
+ }
2470
2739
 
2471
- Han.correctBasicBD = function( context, all ) {
2472
- if ( Han.support.unicoderange && !all ) return
2740
+ if ( $jinze ) {
2741
+ $$prevCS = locateCS( $jinze, {
2742
+ prev: prev,
2743
+ 'class': trimBDClass($bd.getAttribute( 'class' ))
2744
+ })
2745
+ }
2746
+ return portion.text
2747
+ }
2473
2748
 
2474
- var context = context || document
2475
- var finder
2749
+ function insertJiyaCS( $jinze ) {
2750
+ if (
2751
+ matches( $jinze, '.tou, .touwei' ) &&
2752
+ !matches( $jinze.previousSibling, 'h-cs.jiya-outer' )
2753
+ ) {
2754
+ $jinze.insertAdjacentHTML( 'beforebegin', JIYA_CS_HTML )
2755
+ }
2756
+ if (
2757
+ matches( $jinze, '.wei, .touwei' ) &&
2758
+ !matches( $jinze.nextSibling, 'h-cs.jiya-outer' )
2759
+ ) {
2760
+ $jinze.insertAdjacentHTML( 'afterend', JIYA_CS_HTML )
2761
+ }
2762
+ }
2763
+
2764
+ function locateCS( $jinze, attr ) {
2765
+ var $prev, $next
2766
+
2767
+ if (matches( $jinze, '.tou, .touwei' )) {
2768
+ $prev = $jinze.previousSibling
2769
+
2770
+ if (matches( $prev, 'h-cs' )) {
2771
+ $prev.className = 'jinze-outer jiya-outer'
2772
+ $prev.setAttribute( 'prev', attr.prev )
2773
+ }
2774
+ }
2775
+ if (matches( $jinze, '.wei, .touwei' )) {
2776
+ $next = $jinze.nextSibling
2777
+
2778
+ if (matches( $next, 'h-cs' )) {
2779
+ $next.className = 'jinze-outer jiya-outer ' + attr[ 'class' ]
2780
+ $next.removeAttribute( 'prev' )
2781
+ }
2782
+ }
2783
+ return [ $prev, $next ]
2784
+ }
2476
2785
 
2477
- finder = Han.find( context )
2786
+ Han.renderJiya = function( context ) {
2787
+ var context = context || document
2788
+ var finder = Han.find( context )
2478
2789
 
2479
2790
  finder
2480
- .wrap( /\u00B7/g, $.clone( mdot ))
2481
- .charify({ biaodian: true })
2791
+ .avoid( 'textarea, code, kbd, samp, pre, h-cs' )
2792
+
2793
+ .avoid( JIYA_AVOID )
2794
+ .charify({
2795
+ avoid: false,
2796
+ biaodian: charifyBiaodian
2797
+ })
2798
+ // End avoiding `JIYA_AVOID`:
2799
+ .endAvoid()
2800
+
2801
+ .avoid( 'textarea, code, kbd, samp, pre, h-cs' )
2802
+ .replace( TYPESET.group.biaodian[0], locateConsecutiveBD )
2803
+ .replace( TYPESET.group.biaodian[1], locateConsecutiveBD )
2804
+
2805
+ return finder
2482
2806
  }
2483
2807
 
2484
2808
  $.extend( Han.fn, {
2485
- basicBD: null,
2486
-
2487
- correctBasicBD: function( all ) {
2488
- this.basicBD = Han.correctBasicBD( this.context, all )
2809
+ renderJiya: function() {
2810
+ Han.renderJiya( this.context )
2489
2811
  return this
2490
2812
  },
2491
2813
 
2492
- revertBasicBD: function() {
2493
- try {
2494
- this.basicBD.revert( 'all' )
2495
- } catch (e) {}
2814
+ revertJiya: function() {
2815
+ $.qsa(
2816
+ 'h-char.bd-jiya, h-cs.jiya-outer',
2817
+ this.context
2818
+ ).forEach(function( $elmt ) {
2819
+ var classList = $elmt.classList
2820
+ classList.remove( 'bd-jiya' )
2821
+ classList.remove( 'jiya-outer' )
2822
+ })
2496
2823
  return this
2497
2824
  }
2498
2825
  })