Split math-scribble.rkt into dollar.rkt and asymptote.rkt. Added documentation, added possibility to use KaTeX instead of MathJax

This commit is contained in:
Georges Dupéron 2016-08-10 23:44:43 +02:00
parent 8e4abf4f1e
commit 03bdcc592f
6 changed files with 541 additions and 209 deletions

57
asymptote.rkt Normal file
View File

@ -0,0 +1,57 @@
#lang racket
(require scribble/manual
file/md5
racket/system)
(provide asymptote)
(define (asymptote #:cache [cache? #t] s . strs)
(define single-str
(with-output-to-string
(lambda () (for ([str (in-list `(,s . ,strs))])
(displayln str)))))
(if cache?
;; cache:
(let* ([asymptote-dir "asymptote-images"]
[md (bytes->string/utf-8 (md5 single-str))]
[asy-name (string-append md ".asy")]
[asy-path (build-path asymptote-dir asy-name)]
[png-name (string-append md ".png")]
[png-path (build-path asymptote-dir png-name)]
[eps-name (string-append md ".eps")]
[eps-path (build-path asymptote-dir eps-name)]
[pdf-name (string-append md ".pdf")]
[pdf-path (build-path asymptote-dir pdf-name)]
[svg-name (string-append md ".svg")]
[svg-path (build-path asymptote-dir svg-name)])
(display (current-directory)) (display md) (newline)
;; create dir if neccessary
(unless (directory-exists? asymptote-dir)
(make-directory asymptote-dir))
;; save asymptote code to <md5-of-input>.asy
(with-output-to-file asy-path
(lambda () (display single-str))
#:exists 'replace)
(parameterize ([current-directory (build-path (current-directory)
asymptote-dir)])
;; run asymptote to generate eps
(unless (file-exists? svg-name)
(system (format "asy -v -f svg ~a" asy-name)))
;; run asymptote to generate pdf
(unless (file-exists? pdf-name)
(system (format "asy -v -f pdf ~a" asy-name)))
;; run asymptote to generate png
(unless (file-exists? png-name)
(system (format "asy -v -f png ~a" asy-name)))
(image (build-path asymptote-dir md)
#:suffixes (list ".pdf" ".svg" ".png"))))
;; no cache:
(let ([tmp-file (make-temporary-file "asy-~a.png")])
(with-input-from-string
single-str
(λ ()
;(with-output-to-string
(system (format "asy -v -f png -o ~a" tmp-file))))
tmp-file))) ; HTML png PDF pdf

223
dollar.rkt Normal file
View File

@ -0,0 +1,223 @@
#lang racket/base
(require scribble/manual
scribble/core
scribble/html-properties
scribble/latex-properties
scriblib/render-cond
racket/runtime-path
setup/collects)
(provide $
$$
$-html-handler
$$-html-handler
$-katex
$$-katex
$-mathjax
$$-mathjax
use-katex
use-mathjax
with-html5)
;; KaTeX does not work well with the HTML 4.01 Transitional loose DTD,
;; so we define a style modifier which replaces the prefix for HTML rendering.
(define (with-html5 doc-style)
(define has-html-defaults? (memf html-defaults? (style-properties doc-style)))
(define new-properties
(if has-html-defaults?
(map (λ (s)
(if (html-defaults? s)
(html-defaults (path->collects-relative
(collection-file-path "html5-prefix.html"
"scribble-math"))
(html-defaults-style-path s)
(html-defaults-extra-files s))
s))
(style-properties doc-style))
(cons (html-defaults (path->collects-relative
(collection-file-path "html5-prefix.html"
"scribble-math"))
#f
'()))))
(style (style-name doc-style)
new-properties))
(define-runtime-path mathjax-dir "MathJax")
(define-runtime-path katex-dir "katex")
#|
(define mathjax-dir
(path->collects-relative
(collection-file-path "MathJax" "scribble-math")))
|#
(define (load-script-string src)
(string-append
#<<eojs
(function() {
document.write('<scr' + 'ipt type="text/javascript" src="
eojs
src
#<<eojs
"></scr' + 'ipt>');
})();
eojs
))
(define (load-style-string src)
(string-append
#<<eojs
(function() {
document.write('<link rel="stylesheet" href="
eojs
src
#<<eojs
" />');
})();
eojs
))
(define load-mathjax-code
(string->bytes/utf-8
(load-script-string "MathJax/MathJax.js?config=default")))
(define load-katex-code+style
(string->bytes/utf-8
(string-append (load-style-string "katex/katex.min.css")
(load-script-string "katex/katex.min.js")
#<<eojs
(function(f) {
// A "simple" onLoad function
if (window.document.readyState == "complete") {
f();
} else if (window.document.addEventListener) {
window.document.addEventListener("DOMContentLoaded", f, false);
} else if (window.attachEvent) {
window.attachEvent("onreadystatechange", function() {
if (window.document.readyState == "complete") {
f();
}
});
} else {
var oldLoad = window.onload;
if (typeof(oldLoad) == "function") {
window.onload = function() {
try {
oldLoad();
} finally {
f();
}
};
} else {
window.onload = f;
}
}
})(function() {
// This is an ugly way to change the doctype, in case the scribble document
// did not use (with-html5).
if (!(document.doctype && document.doctype.publicId == '')) {
if (console && console.log) {
console.log("Re-wrote the document to use the HTML5 doctype.\n"
+ " Consider using the following declaration:\n"
+ " @title[#:style (with-html5 manual-doc-style)]{…}");
}
var wholeDoc = '<!doctype HTML>\n' + document.documentElement.outerHTML;
document.open();
document.clear();
document.write(wholeDoc);
}
var inlineElements = document.getElementsByClassName("tex-math-inline");
for (var i = 0; i < inlineElements.length; i++) {
var e = inlineElements[i];
katex.render(e.textContent, e, { displayMode:false, throwOnError:false });
}
var displayElements = document.getElementsByClassName("tex-math-display");
for (var i = 0; i < displayElements.length; i++) {
var e = displayElements[i];
katex.render(e.textContent, e, { displayMode:true, throwOnError:false });
}
});
eojs
)))
(define math-inline-style-mathjax
(make-style "tex-math"
(list #;(make-css-addition math-inline.css)
#;(make-tex-addition math-inline.tex)
(install-resource mathjax-dir)
(js-addition load-mathjax-code)
'exact-chars)))
(define math-display-style-mathjax
(make-style "tex-math"
(list #;(make-css-addition math-inline.css)
#;(make-tex-addition math-inline.tex)
(install-resource mathjax-dir)
(js-addition load-mathjax-code)
'exact-chars)))
(define math-inline-style-katex
(make-style "tex-math-inline"
(list (install-resource katex-dir)
(js-addition load-katex-code+style)
'exact-chars)))
(define math-display-style-katex
(make-style "tex-math-display"
(list (install-resource katex-dir)
(js-addition load-katex-code+style)
'exact-chars)))
(define ($-mathjax strs)
(make-element math-inline-style-mathjax `("$" ,@strs "$")))
(define ($-katex strs)
(make-element math-inline-style-katex strs))
(define ($$-mathjax strs)
(make-element math-display-style-mathjax `("\\[" ,@strs "\\]")))
(define ($$-katex strs)
(make-element math-display-style-katex strs))
(define $-html-handler (make-parameter $-katex))
(define $$-html-handler (make-parameter $$-katex))
(define (use-katex)
($-html-handler $-katex)
($$-html-handler $$-katex)
(void))
(define (use-mathjax)
($-html-handler $-mathjax)
($$-html-handler $$-mathjax)
(void))
(define ($ s . strs)
(cond-element
[html (($-html-handler) `(,s . ,strs))]
[latex `("$" ,s ,@strs "$")]
;; TODO: use a unicode representation of math, e.g. x^2 becomes x²
[else `(,s . ,strs)]))
(define ($$ s . strs)
(cond-element
[html (($$-html-handler) `(,s . ,strs))]
[latex `("\\[" ,s ,@strs "\\]")]
;; TODO: use a spatial representation of display math, e.g.
;; \sum_{i=0}^n x_i^2
;; becomes:
;; n
;; ───
;; ╲ 2
;; 〉 x
;; i
;; ───
;; i=0
;; Or use a spatial unicode representation, so that the above becomes:
;; n
;; ∑ xᵢ²
;; i=0
[else `(,s . ,strs)]))

1
html5-prefix.html Normal file
View File

@ -0,0 +1 @@
<!DOCTYPE html>

View File

@ -1,35 +1,6 @@
#lang racket/base
#lang at-exp racket/base
(module+ test
(require rackunit))
;; Notice
;; To install (from within the package directory):
;; $ raco pkg install
;; To install (once uploaded to pkgs.racket-lang.org):
;; $ raco pkg install <<name>>
;; To uninstall:
;; $ raco pkg remove <<name>>
;; To view documentation:
;; $ raco docs <<name>>
;;
;; For your convenience, we have included a LICENSE.txt file, which links to
;; the GNU Lesser General Public License.
;; If you would prefer to use a different license, replace LICENSE.txt with the
;; desired license.
;;
;; Some users like to add a `private/` directory, place auxiliary files there,
;; and require them in `main.rkt`.
;;
;; See the current version of the racket style guide here:
;; http://docs.racket-lang.org/style/index.html
;; Code here
(module+ test
;; Tests to be run with raco test
)
(module+ main
;; Main entry point, executed when run with the `racket` executable or DrRacket.
)
(require scribble-math/dollar
scribble-math/asymptote)
(provide (all-from-out scribble-math/dollar
scribble-math/asymptote))

View File

@ -1,172 +0,0 @@
#lang racket
;;;
;;; Support for MathJax and Asymptote.
;;;
(require scribble/manual
scribble/core
scribble/decode
scribble/html-properties
scribble/latex-properties
racket/runtime-path
file/md5)
(provide $ $$ asymptote theorem proof corollary boxed exercise definition example remark
element html-only exact chapter subchapter subsubchapter pi)
(define-runtime-path math-inline.css "math-inline.css")
(define-runtime-path math-inline.tex "math-inline.tex")
(define-runtime-path math-display.css "math-display.css")
(define-runtime-path math-display.tex "math-display.tex")
(define-runtime-path boxed.css "boxed.css")
(define-runtime-path boxed.tex "boxed.tex")
(define-runtime-path htmlonly.tex "htmlonly.tex")
(define-runtime-path htmlonly.css "htmlonly.css")
(define html-only-style
(make-style "HTMLOnly"
(list (make-css-addition htmlonly.css)
(make-tex-addition htmlonly.tex)
'exact-chars)))
(define (html-only s . strs)
(make-element
html-only-style
(cons s strs)))
(define (exact s . strs)
(newline)
(map display (cons s strs))
(newline)
(make-element (make-style "identity" '(exact-chars))
(cons s strs)))
(define math-inline-style
(make-style "MathInline"
(list (make-css-addition math-inline.css)
(make-tex-addition math-inline.tex)
'exact-chars)))
(define math-display-style
(make-style "MathDisplay"
(list (make-css-addition math-display.css)
(make-tex-addition math-display.tex)
'exact-chars)))
#;(define boxed-style
(make-style "Boxed"
(list (make-css-addition boxed.css)
(make-tex-addition boxed.tex))))
(define ($ s . strs)
(make-element
math-inline-style
(cons "$" (cons s (append strs '("$"))))))
(define ($$ s . strs)
(make-element
math-display-style
(cons "\\[" (cons s (append strs '("\\]"))))))
(define (asymptote s . strs)
(define asymptote-dir "asymptote-images")
(let* ([strs (apply string-append (cons s strs))]
[md (bytes->string/utf-8 (md5 strs))]
[asy-name (string-append md ".asy")]
[asy-path (build-path asymptote-dir asy-name)]
[png-name (string-append md ".png")]
[png-path (build-path asymptote-dir png-name)]
[eps-name (string-append md ".eps")]
[eps-path (build-path asymptote-dir eps-name)]
[pdf-name (string-append md ".pdf")]
[pdf-path (build-path asymptote-dir pdf-name)]
[svg-name (string-append md ".svg")]
[svg-path (build-path asymptote-dir svg-name)])
(display (current-directory)) (display md) (newline)
;; create dir if neccessary
(unless (directory-exists? asymptote-dir)
(make-directory asymptote-dir))
;; save asymptote code to <md5-of-input>.asy
(with-output-to-file asy-path
(lambda () (display strs) (newline))
#:exists 'replace)
(parameterize ([current-directory (build-path (current-directory) asymptote-dir)])
;; run asymptote to generate eps
(unless (file-exists? svg-name)
(system (format "asy -f svg ~a" asy-name)))
;; run asymptote to generate pdf
(unless (file-exists? pdf-name)
(system (format "asy -v -f pdf ~a" asy-name)))
;; run asymptote to generate png
(unless (file-exists? png-name)
(system (format "asy -v -f png ~a" asy-name)))
;(image png-path #:suffixes (list ".png" #;".pdf" )) ; HTML pdf PDF pdf
;(image svg-path #:suffixes (list ".svg" #;".pdf" )) ; HTML pdf PDF pdf
(image (build-path asymptote-dir md) #:suffixes (list ".pdf" ".svg" ".png")) ; HTML png PDF pdf
; (image (build-path svg-path) #:suffixes (list ".svg" ".pdf" ".png"))
)))
(define (boxed s . strs)
(let ([s (cons s strs)])
(make-element boxed-style (decode-content s))))
(define (definition s . strs)
(let ([ss strs])
(make-paragraph plain
(cons (make-element 'bold "Definition ")
(cons (make-element 'italic s)
(decode-content ss))))))
(define (theorem s . strs)
(let ([ss strs])
(make-paragraph plain
(cons (make-element 'bold "Sætning ")
(cons (make-element 'italic s)
(decode-content ss))))))
(define (example . strs)
(let ([s (if (not (null? strs)) (car strs) "")]
[ss (if (not (null? strs)) (cdr strs) null)])
(make-paragraph plain
(cons (make-element 'bold "Eksempel ")
(cons (make-element 'italic s)
(decode-content ss))))))
(define (corollary s . strs)
(let ([s (cons s strs)])
(make-paragraph plain
(cons (make-element 'bold "Følgesætning ")
(decode-content s)))))
(define (proof . s)
(make-paragraph plain
(cons (make-element 'bold "Bevis ")
(decode-content s))))
(define (remark . s)
(make-paragraph plain
(cons (make-element 'bold "Bemærkning ")
(decode-content s))))
;;; One large page
;(define chapter section)
;(define subchapter subsection)
;(define subsubchapter subsubsection)
;;; Several small pages
(define chapter section)
(define subchapter section)
(define subsubchapter subsection)
(define (exercise . s)
(make-paragraph plain
(cons (make-element 'bold "Opgave ")
(decode-content s))))

View File

@ -1,10 +1,262 @@
#lang scribble/manual
@require[@for-label[scribble-math
racket/base]]
racket/base
scribble/core]
@for-syntax[racket/base
syntax/parse]
scribble-math]
@title{scribble-math}
@(define-syntax scribbleblock
(syntax-parser
[(_ (~optional (~seq #:keep-lang-line? keep-lang))
str ...+)
#`(codeblock
#:keep-lang-line? #,(if (attribute keep-lang) #'keep-lang #'#f)
"#lang scribble/base" "\n"
str ...)]))
@(define-syntax scribblecode
(syntax-parser
[(_ str ...+)
#`(code #:lang "scribble/base"
str ...)]))
@(use-mathjax)
@title[#:style (with-html5 manual-doc-style)]{scribble-math}
@author{georges}
@defmodule[scribble-math]
Package Description Here
This library allows typesetting math and Asymptote figures
in Scribble documents.
@(local-table-of-contents #:style 'immediate-only)
@section{Typesetting math with @racket[$] and @racket[$$]}
@defmodule[scribble-math/dollar]
@(define title-html5-code
@scribblecode|{@title[#:style (with-html5 manual-doc-style)]{…}}|)
The following functions help with typesetting mathematical
equations. The main functions are @racket[$] for inline mode
math, @racket[$$] for display mode math. The functions
@racket[use-katex] and @racket[use-mathjax] change the
rendering engine used, the default being @racket[katex]. To
use @racket[katex], it is necessary to use
@title-html5-code or a similar configuration, see the
documentation for @racket[with-html5] for more details.
@defproc[($ [str string?] ...) element?]{
Renders the given strings as inline math, using MathJax or
KaTeX for the HTML output, depending on the current
configuration. For the LaTeX output, the code is simply
passed as-is. For example, when using MathJax,
@racket[($ "x^2")] renders as
@(use-mathjax) @${x^2}.
The syntax accepted by @racket[$] is a subset of the
commands supported by LaTeX, and depends on the backend
used (MathJax should support more commands than KaTeX). For
details, see their respective documentation.}
@defproc[($$ [str string?] ...) element?]{
Renders the given strings as display math (centered, alone
on its line), using MathJax or KaTeX for the HTML output,
depending on the current configuration. For the LaTeX
output, the code is simply passed as-is. For example, when
using MathJax,
@racketblock[($$ "\\sum_{i=0}^n x_i^3")]
renders as:
@(use-mathjax)
@$${\sum_{i=0}^n x_i^3}
The syntax accepted by @racket[$] is a subset of the
commands supported by LaTeX, and depends on the backend
used (MathJax should support more commands than KaTeX). For
details, see their respective documentation.}
@defproc[(with-html5 [doc-style style?]) style?]{
Alters the given document style, so that the resulting
document uses HTML5.
This function should be called to alter the
@racket[#:style] argument for @racket[title] when KaTeX is
used, as KaTeX is incompatible with the default scribble
@tt{DOCTYPE} (the HTML 4.01 Transitional loose DTD). The
scribble document should therefore contain code similar to
the following:
@scribbleblock|{
@title[#:style (with-html5 manual-doc-style)]{...}
}|
This function works by changing the existing
@racket[html-defaults] property or adding a new one, so
that it uses an HTML5
@tech[#:doc '(lib "scribblings/scribble/scribble.scrbl")]{prefix file}
(the @tech[#:doc '(lib "scribblings/scribble/scribble.scrbl")]{prefix file}
contains the @tt{DOCTYPE} line).}
@defparam[$-html-handler handler (→ (listof? string?) element?)
#:value $-katex]{
A parameter whose values is a function called by
@racket[$], to transform the math code into HTML. The
@racket[$] function uses this parameter only when rendering
the document as HTML.}
@defparam[$$-html-handler handler (→ (listof? string?) element?)
#:value $$-katex]{
A parameter whose values is a function called by
@racket[$], to transform the math code into HTML. The
@racket[$] function uses this parameter only when rendering
the document as HTML.
}
@defproc[($-katex [math (listof? string?)]) element?]{
Produces an @racket[element?] which contains the given
@racket[math] code, so that it is rendered as inline math
using KaTeX. More precisely, the resulting element uses
several scribble properties to add scripts and stylesheets
to the document. The resulting element also uses a specific
CSS class so that when the page is loaded into a browser,
KaTeX can recognise it and render it in inline mode.}
@defproc[($$-katex [math (listof? string?)]) element?]{
Produces an @racket[element?] which contains the given
@racket[math] code, so that it is rendered as display math
(centered, alone on its line) using KaTeX. More precisely,
the resulting element uses several scribble properties to
add scripts and stylesheets to the document. The resulting
element also uses a specific CSS class so that when the
page is loaded into a browser, KaTeX can recognise it and
render it in display mode.}
@defproc[($-mathjax [math (listof? string?)]) element?]{
Produces an @racket[element?] which contains the given
@racket[math] code, so that it is rendered as inline math
using MathJax. More precisely, the resulting element uses
several scribble properties to add scripts and stylesheets
to the document. The resulting element also uses a specific
CSS class so that when the page is loaded into a browser,
MathJax can recognise it and render it in inline mode.}
@defproc[($$-mathjax [math (listof? string?)]) element?]{
Produces an @racket[element?] which contains the given
@racket[math] code, so that it is rendered as display math
(centered, alone on its line) using KaTeX. More precisely,
the resulting element uses several scribble properties to
add scripts and stylesheets to the document. The resulting
element also uses a specific CSS class so that when the
page is loaded into a browser, MathJax can recognise it and
render it in display mode.}
@defproc[(use-katex) void?]{
This shorthand calls @racket[($-html-handler $-katex)]
and @racket[($$-html-handler $$-katex)]. The mathematical
formulas passed to @racket[$] and @racket[$$] which appear
later in the document will therefore be typeset using
KaTeX.
The KaTeX library will be added to the HTML document only
if is uses the result of one of @racket[$], @racket[$$],
@racket[$-katex] or @racket[$$-katex]. It is therefore safe
to call this function in libraries to change the default
handler, without the risk of adding extra resources to the
page if the user changes the default before typesetting any
math.}
@defproc[(use-mathjax) void?]{
This shorthand calls @racket[($-html-handler $-mathjax)]
and @racket[($$-html-handler $$-mathjax)]. The mathematical
formulas passed to @racket[$] and @racket[$$] which appear
later in the document will therefore be typeset using
MathJax.
The MathJax library will be added to the HTML document only
if is uses the result of one of @racket[$], @racket[$$],
@racket[$-katex] or @racket[$$-katex]. It is therefore safe
to call this function in libraries to change the default
handler, without the risk of adding extra resources to the
page if the user changes the default before typesetting any
math.}
@;@$${\sum_{i=0}ⁿ xᵢ³}
When using MathJax, @racket[$] and @racket[$$] wrap their
content with @racket["$…$"] and @racket["\\[…\\]"]
respectively, and insert it in an element with the style
@racket["tex2jax_process"]. MathJax is configured to only
process elements with this class, so it is safe to use
@tt{$} signs in the source document. For example, the text
$\sum x^3$ is displayed as-is, like the rest of the text.
@section{Drawing figures with Asymptote}
@defmodule[scribble-math/asymptote]
@defproc[(asymptote [#:cache cache? any/c] [str string?] ...+) image?]{
Renders the figure described by the given strings using
Asymptote. To improve compilation speed, if
@racket[cache?] is @racket[#f], then the result is cached
in the @filepath{asymptote-images} directory, based on a
checksum of the strings. It is a good idea to clean up the
working directory after experimenting a lot with a figure,
as it will be cluttered with stale cached files. Otherwise,
temporary PNG, SVG and PDF files are generated using
@racket[make-temporary-file].
If the Asymptote code is dynamically generated, make sure
that the result is always the same, or use
@racket[#:cache #f]. Otherwise, each compilation would
cause a new file to be generated.
The @tt{asy} executable must be installed on the
machine that renders the figures. If the results are
already cached, then the scribble document can be compiled
without installing Asymptote.
As an example, the the code
@scribbleblock|{
@asymptote{
import drawtree;
size(4cm, 0);
TreeNode root = makeNode("let");
TreeNode bindings = makeNode(root, "bindings");
TreeNode binding = makeNode(bindings, "binding");
TreeNode bid = makeNode(binding, "id");
TreeNode bexpr = makeNode(binding, "expr");
TreeNode bindingddd = makeNode(bindings, "\vphantom{x}\dots");
TreeNode body = makeNode(root, "body");
TreeNode bodyddd = makeNode(root, "\vphantom{x}\dots");
draw(root, (0,0));
shipout(scale(2)*currentpicture.fit());
}
}|
renders as:
@asymptote{
import drawtree;
size(4cm, 0);
TreeNode root = makeNode("let");
TreeNode bindings = makeNode(root, "bindings");
TreeNode binding = makeNode(bindings, "binding");
TreeNode bid = makeNode(binding, "id");
TreeNode bexpr = makeNode(binding, "expr");
TreeNode bindingddd = makeNode(bindings, "\vphantom{bg}\dots");
TreeNode body = makeNode(root, "body");
TreeNode bodyddd = makeNode(root, "\vphantom{bg}\dots");
draw(root, (0,0));
shipout(scale(2)*currentpicture.fit());
}
}