55codeInput . plugins . SpecialChars = class extends codeInput . Plugin {
66 specialCharRegExp ;
77
8+ cachedColors ; // ascii number > [background color, text color]
9+ cachedWidths ; // character > character width
10+ canvasContext ;
11+
812 /**
913 * Create a special characters plugin instance
1014 * @param {RegExp } specialCharRegExp The regular expression which matches special characters
1115 */
1216 constructor ( specialCharRegExp = / (? ! \n ) (? ! \t ) [ \u{0000} - \u{001F} ] | [ \u{0080} - \u{FFFF} ] / ug) { // By default, covers many non-renderable ASCII characters
1317 super ( ) ;
1418 this . specialCharRegExp = specialCharRegExp ;
19+ this . cachedColors = { } ;
20+
21+ this . cachedWidths = { } ;
22+
23+ let canvas = document . createElement ( "canvas" ) ;
24+ window . addEventListener ( "load" , ( ) => {
25+ document . body . appendChild ( canvas ) ;
26+
27+ } ) ;
28+ this . canvasContext = canvas . getContext ( "2d" ) ;
29+ this . canvasContext . fillStyle = "black" ;
30+ this . canvasContext . font = "20px 'Consolas'" ; // TODO: Make dynamic
1531 }
1632
1733 /* Runs after code is highlighted; Params: codeInput element) */
1834 afterHighlight ( codeInput ) {
1935 let result_element = codeInput . querySelector ( "pre code" ) ;
20-
21- console . log ( "Text" , result_element . innerHTML ) ;
22-
23- result_element . innerHTML = result_element . innerHTML . replaceAll ( this . specialCharRegExp , this . specialCharReplacer ) ;
36+ result_element . innerHTML = result_element . innerHTML . replaceAll ( this . specialCharRegExp , this . specialCharReplacer . bind ( this ) ) ;
2437 }
2538
2639 specialCharReplacer ( match_char , _match_char , index , whole_string , groups ) {
27- let hex_code = match_char . codePointAt ( 0 ) . toString ( 16 ) ;
28- hex_code = ( "0000" + hex_code ) . substring ( hex_code . length ) ;
40+ let hex_code = match_char . codePointAt ( 0 ) ;
41+
42+ let colors = this . getCharacterColor ( hex_code ) ;
43+
44+ hex_code = hex_code . toString ( 16 ) ;
45+ hex_code = ( "00" + hex_code ) . substring ( hex_code . length ) ; // So 2 chars with leading 0
2946 hex_code = hex_code . toUpperCase ( ) ;
30- console . log ( match_char , hex_code ) ;
31- return `<span class='code-input_special-char' data-top='${ hex_code . substr ( 0 , 2 ) } ' data-bottom='${ hex_code . substr ( 2 , 2 ) } '> </span>` ;
47+
48+ let char_width = this . getCharacterWidth ( match_char ) ;
49+ console . log ( hex_code , char_width ) ;
50+
51+ // let background_color = hashed_last_hex_char + hashed_last_hex_char + hex_code.substr(0, 1) + hex_code.substr(0, 1) + hex_code.substr(1, 1) + hex_code.substr(1, 1) // So good range of colours
52+ return `<span class='code-input_special-char' data-top='${ hex_code [ 0 ] } ' data-bottom='${ hex_code [ 1 ] } ' style='background-color: #${ colors [ 0 ] } ; color: ${ colors [ 1 ] } ; width: ${ char_width } px'></span>` ;
3253 }
3354
55+ getCharacterColor ( ascii_code ) {
56+ // Choose colors based on character code - lazy load and return [background color, text color]
57+ let background_color ;
58+ let text_color ;
59+ if ( ! ( ascii_code in this . cachedColors ) ) {
60+ // Get background color - arbitrary bit manipulation to get a good range of colours
61+ background_color = ascii_code ^ ( ascii_code << 3 ) ^ ( ascii_code << 7 ) ^ ( ascii_code << 14 ) ^ ( ascii_code << 16 ) ; // Arbitrary
62+ background_color = background_color ^ 0x1fc627 ; // Arbitrary
63+ background_color = background_color . toString ( 16 ) ;
64+ background_color = ( "000000" + background_color ) . substring ( background_color . length ) ; // So 6 chars with leading 0
65+
66+ // Get most suitable text color - white or black depending on background brightness
67+ let color_brightness = 0 ;
68+ for ( let i = 0 ; i < 6 ; i += 2 ) {
69+ color_brightness += parseInt ( background_color . substring ( i , i + 2 ) , 16 ) ;
70+ }
71+ // Calculate darkness
72+ text_color = color_brightness < ( 128 * 3 ) ? "white" : "black" ;
73+
74+ this . cachedColors [ ascii_code ] = [ background_color , text_color ] ;
75+ return [ background_color , text_color ] ;
76+ } else {
77+ return this . cachedColors [ ascii_code ] ;
78+ }
79+ }
80+
81+ getCharacterWidth ( char ) {
82+ // Lazy-load - TODO: Get a cleaner way of doing this
83+ if ( char in this . cachedWidths ) {
84+ return this . cachedWidths [ char ] ;
85+ }
86+
87+ this . canvasContext . fillText ( char , 10 , 20 ) ;
88+ let width = this . canvasContext . measureText ( char ) . width ;
89+ if ( width > 20 ) {
90+ width /= 2 ; // Fix double-width-in-canvas Firefox bug
91+ } else if ( width == 0 ) {
92+ let fallbackWidth = this . getCharacterWidth ( "\u0096" ) ;
93+ this . cachedWidths [ char ] = fallbackWidth ;
94+ return fallbackWidth ; // In Firefox some control chars don't render
95+ }
96+ console . log ( char , this . canvasContext . measureText ( char ) ) ;
97+
98+ this . cachedWidths [ char ] = width ;
99+ return width ;
100+ }
34101}
0 commit comments