@@ -3,48 +3,104 @@ import request from 'request';
33import * as classes from './classes.js' ;
44import UnicodeTrieBuilder from 'unicode-trie/builder.js' ;
55
6- // this loads the LineBreak.txt file for Unicode and parses it to
7- // combine ranges and generate JavaScript
8- request ( 'https://www.unicode.org/Public/15.0.0/ucd/LineBreak.txt' , function ( err , res , data ) {
9- const matches = data . match ( / ^ [ 0 - 9 A - F ] + ( \. \. [ 0 - 9 A - F ] + ) ? ; [ A - Z ] [ A - Z 0 - 9 ] ( [ A - Z ] ) ? / gm) ;
10-
11- let start = null ;
12- let end = null ;
13- let type = null ;
14- const trie = new UnicodeTrieBuilder ( classes . XX ) ;
15-
16- // collect entries in the linebreaking table into ranges
17- // to keep things smaller.
18- for ( let match of matches ) {
19- var rangeEnd , rangeType ;
20- match = match . split ( / ; | \. \. / ) ;
21- const rangeStart = match [ 0 ] ;
22-
23- if ( match . length === 3 ) {
24- rangeEnd = match [ 1 ] ;
25- rangeType = match [ 2 ] ;
26- } else {
27- rangeEnd = rangeStart ;
28- rangeType = match [ 1 ] ;
29- }
6+ const BASE = 'https://www.unicode.org/Public/15.0.0/ucd/' ;
307
31- if ( ( type != null ) && ( rangeType !== type ) ) {
32- trie . setRange ( parseInt ( start , 16 ) , parseInt ( end , 16 ) , classes [ type ] , true ) ;
33- type = null ;
8+ // Build a predicate for East Asian Wide/Full/Halfwidth code points. Rule LB30
9+ // treats opening punctuation and closing parens differently when they are East
10+ // Asian, so those need to be split from the OP/CP classes.
11+ function parseEastAsianWide ( data ) {
12+ const ranges = [ ] ;
13+ const re = / ^ ( [ 0 - 9 A - F ] + ) (?: \. \. ( [ 0 - 9 A - F ] + ) ) ? \s * ; \s * ( [ W F H ] ) \b / gm;
14+ let match ;
15+ while ( ( match = re . exec ( data ) ) !== null ) {
16+ const start = parseInt ( match [ 1 ] , 16 ) ;
17+ const end = match [ 2 ] ? parseInt ( match [ 2 ] , 16 ) : start ;
18+ ranges . push ( [ start , end ] ) ;
19+ }
20+ ranges . sort ( ( a , b ) => a [ 0 ] - b [ 0 ] ) ;
21+ return function isEastAsianWide ( cp ) {
22+ let lo = 0 ;
23+ let hi = ranges . length - 1 ;
24+ while ( lo <= hi ) {
25+ const mid = ( lo + hi ) >> 1 ;
26+ if ( cp < ranges [ mid ] [ 0 ] ) {
27+ hi = mid - 1 ;
28+ } else if ( cp > ranges [ mid ] [ 1 ] ) {
29+ lo = mid + 1 ;
30+ } else {
31+ return true ;
32+ }
3433 }
34+ return false ;
35+ } ;
36+ }
3537
36- if ( type == null ) {
37- start = rangeStart ;
38- type = rangeType ;
38+ function setTypedRange ( trie , start , end , type , isEastAsianWide ) {
39+ // Opening punctuation is set one code point at a time so the East Asian ones
40+ // can be assigned OP_EA for Rule LB30's East Asian exception.
41+ if ( type === 'OP' ) {
42+ for ( let cp = start ; cp <= end ; cp ++ ) {
43+ trie . setRange ( cp , cp , isEastAsianWide ( cp ) ? classes . OP_EA : classes . OP , true ) ;
3944 }
40-
41- end = rangeEnd ;
45+ } else {
46+ trie . setRange ( start , end , classes [ type ] , true ) ;
4247 }
48+ }
49+
50+ function fail ( name , err , res , body ) {
51+ if ( err ) throw err ;
52+ if ( ! res || res . statusCode !== 200 ) throw new Error ( `${ name } : unexpected status ${ res && res . statusCode } ` ) ;
53+ if ( ! body ) throw new Error ( `${ name } : empty response` ) ;
54+ }
55+
56+ request ( `${ BASE } EastAsianWidth.txt` , function ( eawErr , eawRes , eawData ) {
57+ fail ( 'EastAsianWidth.txt' , eawErr , eawRes , eawData ) ;
58+ const isEastAsianWide = parseEastAsianWide ( eawData ) ;
59+
60+ // this loads the LineBreak.txt file for Unicode and parses it to
61+ // combine ranges and generate JavaScript
62+ request ( `${ BASE } LineBreak.txt` , function ( err , res , data ) {
63+ fail ( 'LineBreak.txt' , err , res , data ) ;
64+ const matches = data . match ( / ^ [ 0 - 9 A - F ] + ( \. \. [ 0 - 9 A - F ] + ) ? ; [ A - Z ] [ A - Z 0 - 9 ] ( [ A - Z ] ) ? / gm) ;
65+
66+ let start = null ;
67+ let end = null ;
68+ let type = null ;
69+ const trie = new UnicodeTrieBuilder ( classes . XX ) ;
70+
71+ // collect entries in the linebreaking table into ranges
72+ // to keep things smaller.
73+ for ( let match of matches ) {
74+ var rangeEnd , rangeType ;
75+ match = match . split ( / ; | \. \. / ) ;
76+ const rangeStart = match [ 0 ] ;
77+
78+ if ( match . length === 3 ) {
79+ rangeEnd = match [ 1 ] ;
80+ rangeType = match [ 2 ] ;
81+ } else {
82+ rangeEnd = rangeStart ;
83+ rangeType = match [ 1 ] ;
84+ }
85+
86+ if ( ( type != null ) && ( rangeType !== type ) ) {
87+ setTypedRange ( trie , parseInt ( start , 16 ) , parseInt ( end , 16 ) , type , isEastAsianWide ) ;
88+ type = null ;
89+ }
90+
91+ if ( type == null ) {
92+ start = rangeStart ;
93+ type = rangeType ;
94+ }
95+
96+ end = rangeEnd ;
97+ }
4398
44- trie . setRange ( parseInt ( start , 16 ) , parseInt ( end , 16 ) , classes [ type ] , true ) ;
99+ setTypedRange ( trie , parseInt ( start , 16 ) , parseInt ( end , 16 ) , type , isEastAsianWide ) ;
45100
46- // write the trie Uint8Array to a file
47- const trieBuffer = trie . toBuffer ( ) ;
48- const output = `export default new Uint8Array([${ [ ...trieBuffer ] . join ( ',' ) } ]);\n` ;
49- fs . writeFileSync ( new URL ( 'classes-trie-data.js' , import . meta. url ) , output ) ;
101+ // write the trie Uint8Array to a file
102+ const trieBuffer = trie . toBuffer ( ) ;
103+ const output = `export default new Uint8Array([${ [ ...trieBuffer ] . join ( ',' ) } ]);\n` ;
104+ fs . writeFileSync ( new URL ( 'classes-trie-data.js' , import . meta. url ) , output ) ;
105+ } ) ;
50106} ) ;
0 commit comments