dollar.rkt (13193B)
1 #lang racket/base 2 3 (require scribble/manual 4 scribble/core 5 scribble/html-properties 6 scribble/latex-properties 7 scriblib/render-cond 8 racket/runtime-path 9 setup/collects 10 "katex-convert-unicode.rkt" 11 "mathjax-convert-unicode.rkt" 12 racket/list 13 (only-in xml cdata) 14 (only-in racket/match match) 15 (only-in racket/system process) 16 (only-in racket/port port->string) 17 (for-syntax racket/base)) 18 19 (provide $ 20 $$ 21 $-html-handler 22 $$-html-handler 23 $-katex 24 $$-katex 25 $-mathjax 26 $$-mathjax 27 use-katex 28 use-mathjax 29 with-html5 30 use-external-katex 31 use-external-mathjax) 32 33 (define-syntax (if-version≥6.12 stx) 34 (syntax-case stx () 35 [(_ . rest) 36 (if (and (not (regexp-match #px"^6\\.11\\.0\\.900$" (version))) 37 (or (regexp-match #px"^6(\\.([0123456789]|10|11)(\\..*|)|)$" (version)) 38 (regexp-match #px"^[123245]\\..*$" (version)))) 39 #'(begin) 40 #'(begin . rest))])) 41 42 (if-version≥6.12 43 (provide $-tex2svg 44 $$-tex2svg 45 use-tex2svg 46 current-tex2svg-path)) 47 48 (define use-external-mathjax (make-parameter #f)) 49 (define use-external-katex (make-parameter #f)) 50 51 ;; KaTeX does not work well with the HTML 4.01 Transitional loose DTD, 52 ;; so we define a style modifier which replaces the prefix for HTML rendering. 53 (define (with-html5 doc-style) 54 (define has-html-defaults? (memf html-defaults? (style-properties doc-style))) 55 (define new-properties 56 (if has-html-defaults? 57 (map (λ (s) 58 (if (html-defaults? s) 59 (html-defaults (path->collects-relative 60 (collection-file-path "html5-prefix.html" 61 "scribble-math")) 62 (html-defaults-style-path s) 63 (html-defaults-extra-files s)) 64 s)) 65 (style-properties doc-style)) 66 (cons (html-defaults (path->collects-relative 67 (collection-file-path "html5-prefix.html" 68 "scribble-math")) 69 #f 70 '())))) 71 (style (style-name doc-style) 72 new-properties)) 73 74 75 ;; Other possible sources for MathJax: 76 ;"http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-MML_HTMLorMML" 77 ;"http://c328740.r40.cf1.rackcdn.com/mathjax/latest/MathJax.js?config=default" 78 ;"http://cdn.mathjax.org/mathjax/latest/MathJax.js?config=TeX-AMS-HTML" 79 80 (define-runtime-path mathjax-dir "MathJax") 81 (define-runtime-path katex-dir "katex") 82 #| 83 (define mathjax-dir 84 (path->collects-relative 85 (collection-file-path "MathJax" "scribble-math"))) 86 |# 87 88 89 ;; take into account last scroll event, to avoid locking up the page 90 ;; TODO: should actually pause the whole MathJax queue, as it still locks 91 ;; up the browser between "processing Math" and "rendering math". 92 ;; + set the #MathJax_Message CSS to opacity: 0.5. 93 (define gradually-rename-texMath-to-texMathX-js #<<EOJS 94 (function() { 95 var lastScroll = -1001; 96 scrollEventHandler = function(e) { 97 lastScroll = new Date().getTime(); 98 }; 99 window.addEventListener("scroll", scrollEventHandler); 100 var e = document.getElementsByClassName("texMath"); 101 var pos = 0; 102 var o = {}; 103 process = function() { 104 if (pos < e.length){ 105 if (new Date().getTime() - lastScroll > 1000) { 106 if (e.id == "") { e.id = "texMathElement" + pos; } 107 e[pos++].className = "texMathX"; 108 MathJax.Hub.Queue(["Typeset",MathJax.Hub,e.id]); 109 MathJax.Hub.Queue(["next",o]); 110 } else { 111 window.setTimeout(process, 100); 112 } 113 } else { 114 window.removeEventListener("scroll", scrollEventHandler); 115 } 116 }; 117 o.next = function() { 118 window.setTimeout(process, 100); 119 } 120 process(); 121 })(); 122 EOJS 123 ) 124 125 (define (load-script-string src [async-defer #f]) 126 (string-append 127 #<<EOJS 128 (function() {document.write('<scr' + 'ipt type="text/javascript" src=" 129 EOJS 130 src 131 "\"" 132 (if async-defer " async=\"async\" defer=\"defer\" " "") 133 #<<EOJS 134 ></scr' + 'ipt>');})(); 135 EOJS 136 )) 137 138 (define (load-style-string src) 139 (string-append 140 #<<EOJS 141 (function() { 142 document.write('<link rel="stylesheet" href=" 143 EOJS 144 src 145 #<<EOJS 146 " />'); 147 })(); 148 EOJS 149 )) 150 151 (define load-mathjax-code 152 (string->bytes/utf-8 153 ;; To avoid the need to alter the MathJax configuration, add: 154 ;; <script type="text/x-mathjax-config"> 155 ;; MathJax.Hub.Config({ tex2jax: {inlineMath: [['$','$']]} }); 156 ;; </script> 157 (load-script-string (or (use-external-mathjax) "MathJax/MathJax.js?config=default")))) 158 159 #;(define load-mathjax-code 160 (string->bytes/utf-8 161 (string-append (or (use-external-mathjax) "MathJax/MathJax.js?config=default") 162 #<<EOJS 163 (function(f) { 164 // A "simple" onLoad function 165 if (window.document.readyState == "complete") { 166 f(); 167 } else if (window.document.addEventListener) { 168 window.document.addEventListener("DOMContentLoaded", f, false); 169 } else if (window.attachEvent) { 170 window.attachEvent("onreadystatechange", function() { 171 if (window.document.readyState == "complete") { 172 f(); 173 } 174 }); 175 } else { 176 var oldLoad = window.onload; 177 if (typeof(oldLoad) == "function") { 178 window.onload = function() { 179 try { 180 oldLoad(); 181 } finally { 182 f(); 183 } 184 }; 185 } else { 186 window.onload = f; 187 } 188 } 189 })(function() { 190 var wrap = function(elts, opn, cloz) { 191 var eltsX = []; 192 for (var i = 0; i < elts.length; i++) { eltsX[i] = elts[i]; } 193 for (var i = 0; i < eltsX.length; i++) { 194 var opntxt = document.createTextNode(opn); 195 var cloztxt = document.createTextNode(cloz); 196 var e = eltsX[i]; 197 e.insertBefore(opntxt, e.firstChild); 198 e.appendChild(cloztxt); 199 e.className = e.className.replace(/\b(texMathInline|texMathDisplay)\b/g, 200 "texMath"); 201 } 202 }; 203 wrap(document.getElementsByClassName("texMathInline"), "\\(", "\\)"); 204 wrap(document.getElementsByClassName("texMathDisplay"), "\\[", "\\]"); 205 }); 206 EOJS 207 ))) 208 209 (define load-katex-code+style 210 (string->bytes/utf-8 211 (string-append (load-style-string (if (use-external-katex) (cadr (use-external-katex)) "katex/katex.min.css")) 212 (load-script-string (if (use-external-katex) (car (use-external-katex)) "katex/katex.min.js")) 213 #<<EOJS 214 (function(f) { 215 // A "simple" onLoad function 216 if (window.document.readyState == "complete") { 217 f(); 218 } else if (window.document.addEventListener) { 219 window.document.addEventListener("DOMContentLoaded", f, false); 220 } else if (window.attachEvent) { 221 window.attachEvent("onreadystatechange", function() { 222 if (window.document.readyState == "complete") { 223 f(); 224 } 225 }); 226 } else { 227 var oldLoad = window.onload; 228 if (typeof(oldLoad) == "function") { 229 window.onload = function() { 230 try { 231 oldLoad(); 232 } finally { 233 f(); 234 } 235 }; 236 } else { 237 window.onload = f; 238 } 239 } 240 })(function() { 241 // This is an ugly way to change the doctype, in case the scribble document 242 // did not use (with-html5). 243 if (!(document.doctype && document.doctype.publicId == '')) { 244 if (console && console.log) { 245 console.log("Re-wrote the document to use the HTML5 doctype.\n" 246 + " Consider using the following declaration:\n" 247 + " @title[#:style (with-html5 manual-doc-style)]{…}"); 248 } 249 var wholeDoc = '<!doctype HTML>\n' + document.documentElement.outerHTML; 250 document.open(); 251 document.clear(); 252 document.write(wholeDoc); 253 } 254 var inlineElements = document.getElementsByClassName("texMathInline"); 255 for (var i = 0; i < inlineElements.length; i++) { 256 var e = inlineElements[i]; 257 katex.render(e.textContent, e, { displayMode:false, throwOnError:false }); 258 } 259 var displayElements = document.getElementsByClassName("texMathDisplay"); 260 for (var i = 0; i < displayElements.length; i++) { 261 var e = displayElements[i]; 262 katex.render(e.textContent, e, { displayMode:true, throwOnError:false }); 263 } 264 }); 265 EOJS 266 ))) 267 268 (define tex-commands 269 (string->bytes/utf-8 #<<EOTEX 270 \def\math#1{\ensuremath{#1}} 271 \def\texMathInline#1{\ensuremath{#1}} 272 \def\texMathDisplay#1{\ifmmode #1\else\[#1\]\fi} 273 EOTEX 274 )) 275 276 (define math-inline-style-mathjax 277 (style "math" 278 (append (list (alt-tag "span")) 279 #;(list (make-css-addition math-inline.css)) 280 (if (use-external-mathjax) '() (list (install-resource mathjax-dir))) 281 (list (js-addition load-mathjax-code)) 282 (list 'exact-chars)))) 283 284 (define math-display-style-mathjax 285 (style "math" 286 (append (list (alt-tag "div")) 287 #;(list (make-css-addition math-inline.css)) 288 (if (use-external-mathjax) '() (list (install-resource mathjax-dir))) 289 (list (js-addition load-mathjax-code)) 290 (list 'exact-chars)))) 291 292 (define math-inline-style-katex 293 (style "texMathInline" 294 (append (if (use-external-katex) '() (list (install-resource katex-dir))) 295 (list (js-addition load-katex-code+style)) 296 (list 'exact-chars)))) 297 298 (define math-display-style-katex 299 (style "texMathDisplay" 300 (append (if (use-external-katex) '() (list (install-resource katex-dir))) 301 (list (js-addition load-katex-code+style)) 302 (list 'exact-chars)))) 303 304 (define math-inline-style-latex 305 (style "texMathInline" 306 (list (tex-addition tex-commands) 307 'exact-chars))) 308 309 (define math-display-style-latex 310 (style "texMathDisplay" 311 (list (tex-addition tex-commands) 312 'exact-chars))) 313 314 (define ($-mathjax strs) 315 (elem #:style math-inline-style-mathjax strs)) 316 317 (define ($-katex strs) 318 (elem #:style math-inline-style-katex 319 (map (λ (s) (katex-convert-unicode s #t)) (flatten strs)))) 320 321 (if-version≥6.12 322 (define current-tex2svg-path (make-parameter #f)) 323 324 (define (find-tex2svg) 325 (define paths 326 (list 327 "./node_modules/.bin/" 328 "/usr/local/lib/node_modules/mathjax-node-cli/bin/" 329 "/usr/lib/node_modules/mathjax-node-cli/bin/" 330 "/usr/local/bin/" 331 "/usr/local/sbin/" 332 "/usr/bin/" 333 "/usr/sbin/")) 334 (for/or ([path paths]) 335 (file-exists? (format "~a/tex2svg" path)))) 336 337 (define tex2svg 338 (let ([tex2svg-path (find-tex2svg)]) 339 (lambda (#:inline [inline #f] strs) 340 (if (or (current-tex2svg-path) tex2svg-path) 341 (match (process (format 342 "tex2svg ~a'~a'" 343 (if inline "--inline " "") 344 (apply string-append strs))) 345 [`(,stdout . ,_) 346 (port->string stdout)]) 347 (error 'tex2svg "Cannot find tex2svg in path or common places; set path manually with current-tex2svg-path."))))) 348 349 350 (define ($-tex2svg strs) 351 (elem #:style (style #f 352 (list 353 (xexpr-property 354 (cdata #f #f (tex2svg #:inline #t (flatten strs))) 355 (cdata #f #f ""))))))) 356 357 (define ($$-mathjax strs) 358 (elem #:style math-display-style-mathjax strs)) 359 360 (define ($$-katex strs) 361 (elem #:style math-display-style-katex 362 (map (λ (s) (katex-convert-unicode s #t)) (flatten strs)))) 363 364 (if-version≥6.12 365 (define ($$-tex2svg strs) 366 (elem #:style (style #f 367 (list 368 (xexpr-property 369 (cdata #f #f (tex2svg (flatten strs))) 370 (cdata #f #f ""))))))) 371 372 (define $-html-handler (make-parameter $-katex)) 373 (define $$-html-handler (make-parameter $$-katex)) 374 375 (define (use-katex) 376 ($-html-handler $-katex) 377 ($$-html-handler $$-katex) 378 (void)) 379 380 (define (use-mathjax) 381 ($-html-handler $-mathjax) 382 ($$-html-handler $$-mathjax) 383 (void)) 384 385 (if-version≥6.12 386 (define (use-tex2svg) 387 ($-html-handler $-tex2svg) 388 ($$-html-handler $$-tex2svg) 389 (void))) 390 391 (define ($ . strs) 392 (let ([$- ($-html-handler)]) 393 (cond-element 394 [html ($- strs)] 395 [latex (elem #:style math-inline-style-latex strs)] 396 ;; TODO: use a unicode representation of math, e.g. x^2 becomes x² 397 [else strs]))) 398 399 (define ($$ #:latex-style [latex-style math-display-style-latex] . strs) 400 (let ([$$- ($$-html-handler)]) 401 (cond-element 402 [html ($$- strs)] 403 [latex (elem #:style latex-style strs)] 404 ;; TODO: use a spatial representation of display math, e.g. 405 ;; \sum_{i=0}^n x_i^2 406 ;; becomes: 407 ;; n 408 ;; ─── 409 ;; ╲ 2 410 ;; 〉 x 411 ;; ╱ i 412 ;; ─── 413 ;; i=0 414 ;; Or use a spatial unicode representation, so that the above becomes: 415 ;; n 416 ;; ∑ xᵢ² 417 ;; i=0 418 [else strs])))