www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

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])))