Compare commits
33 Commits
main
...
4b4573ee1d
| Author | SHA1 | Date | |
|---|---|---|---|
| 4b4573ee1d | |||
| 071a15ca51 | |||
| f811a519b5 | |||
| fa42052a6a | |||
| ea3b77619a | |||
| cf225f6bc5 | |||
| 2776144f04 | |||
| 71163b9bb5 | |||
| 7d12f7d90e | |||
| d5ccadb326 | |||
| ee86f0d643 | |||
| e67f62e5b0 | |||
| b765f71b55 | |||
| 47b201fd03 | |||
| 8ec78c040a | |||
| d15f9988c2 | |||
| b3ca176ee2 | |||
| efd4c4cdbc | |||
| f6da1187ad | |||
| bd3e6016ed | |||
| 02bbe0a8b8 | |||
| dc31f02b30 | |||
| fd8354f398 | |||
| af493a1291 | |||
| 955db64027 | |||
| ad8ae8f743 | |||
| a52bc97ed3 | |||
| 470dec9183 | |||
| 790ce7e01f | |||
| aa8b89fe84 | |||
| 638b12c9eb | |||
| 9ac8577478 | |||
| b5a5721eed |
6
doerg/doerg-tex/deps.edn
Normal file
6
doerg/doerg-tex/deps.edn
Normal file
@@ -0,0 +1,6 @@
|
||||
{:deps {babashka/fs {:mvn/version "0.5.24"}
|
||||
cheshire/cheshire {:mvn/version "6.1.0"}
|
||||
com.rpl/specter {:mvn/version "1.1.6"}
|
||||
mvxcvi/clj-cbor {:mvn/version "1.1.1"}
|
||||
babashka/process {:mvn/version "0.6.25"}}
|
||||
:paths ["." "classes"]}
|
||||
5
doerg/doerg-tex/deserialise.clj
Normal file
5
doerg/doerg-tex/deserialise.clj
Normal file
@@ -0,0 +1,5 @@
|
||||
(ns deserialise
|
||||
(:require [clj-cbor.core :as cbor]
|
||||
[clojure.string :as str]))
|
||||
|
||||
(prn (cbor/decode cbor/default-codec System/in :eof))
|
||||
@@ -34,13 +34,13 @@ function do_command (cmd) {
|
||||
return null
|
||||
}
|
||||
} catch (e) {
|
||||
return e
|
||||
console.error (e)
|
||||
return {type: "error", error: e}
|
||||
}
|
||||
}
|
||||
|
||||
function main () {
|
||||
const options = commandLineArgs (cli_spec)
|
||||
console.error (options)
|
||||
macros = load_preambles (options.preamble)
|
||||
const decoder = new DecoderStream ()
|
||||
const encoder = new EncoderStream ()
|
||||
|
||||
@@ -8,8 +8,11 @@
|
||||
(defn c [x]
|
||||
(->> x cbor/encode (map #(format "%02x" %)) (str/join " ")))
|
||||
|
||||
#_
|
||||
(w "\\naturalto")
|
||||
|
||||
(w "\\ifxetex blah \\fi")
|
||||
|
||||
#_#_#_
|
||||
(w "c = \\sqrt{a^2 + y^2}")
|
||||
(w "c = \\sqrt{a^ + y^2")
|
||||
|
||||
343
doerg/resources/net/deertopia/doerg/Temml-Plex.css
Normal file
343
doerg/resources/net/deertopia/doerg/Temml-Plex.css
Normal file
@@ -0,0 +1,343 @@
|
||||
/* Based on Temml-local.css. */
|
||||
math {
|
||||
font-family: "IBM Plex Math", "Cambria Math", 'STIXTwoMath-Regular', 'NotoSansMath-Regular', math;
|
||||
font-style: normal;
|
||||
font-weight: normal;
|
||||
line-height: normal;
|
||||
font-size-adjust: none;
|
||||
text-indent: 0;
|
||||
text-transform: none;
|
||||
letter-spacing: normal;
|
||||
word-wrap: normal;
|
||||
direction: ltr;
|
||||
/* Prevent Firefox from omitting the dot on i or j. */
|
||||
font-feature-settings: "dtls" off;
|
||||
}
|
||||
|
||||
math * {
|
||||
border-color: currentColor;
|
||||
}
|
||||
|
||||
/* display: block is necessary in Firefox and Safari.
|
||||
* Not in Chromium, which recognizes display: "block math" written inline. */
|
||||
math.tml-display {
|
||||
display: block;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
*.mathcal {
|
||||
/* NotoSans */
|
||||
font-feature-settings: 'ss01';
|
||||
}
|
||||
|
||||
math .mathscr {
|
||||
font-family: "IBM Plex Math";
|
||||
}
|
||||
|
||||
mo.tml-prime {
|
||||
font-family: "IBM Plex Math";
|
||||
}
|
||||
|
||||
/* Cramped superscripts in WebKit */
|
||||
mfrac > :nth-child(2),
|
||||
msqrt,
|
||||
mover > :first-child {
|
||||
math-shift: compact
|
||||
}
|
||||
|
||||
.menclose {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
padding: 0.5ex 0ex;
|
||||
}
|
||||
.tml-cancelto {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
padding: 0.5ex 0ex;
|
||||
background-image: url("data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg'><defs><marker id='a' markerHeight='5' markerUnits='strokeWidth' markerWidth='7' orient='auto' refX='7' refY='2.5'><path fill='currentColor' d='m0 0 7 2.5L0 5z'/></marker></defs><line x2='100%' y1='100%' stroke='currentColor' stroke-width='.06em' marker-end='url(%23a)' vector-effect='non-scaling-stroke'/></svg>");
|
||||
}
|
||||
|
||||
@supports (-moz-appearance: none) {
|
||||
/* \vec w/o italic correction for Firefox */
|
||||
.tml-vec {
|
||||
transform: scale(0.75)
|
||||
}
|
||||
/* Fix \cancelto in Firefox */
|
||||
.ff-narrow {
|
||||
width: 0em;
|
||||
}
|
||||
.ff-nudge-left {
|
||||
margin-left: -0.2em;
|
||||
}
|
||||
}
|
||||
|
||||
@supports (not (-moz-appearance: none)) {
|
||||
/* Chromium and WebKit */
|
||||
/* prime vertical alignment */
|
||||
mo.tml-prime {
|
||||
font-family: "IBM Plex Math";
|
||||
}
|
||||
/* Italic correction on superscripts */
|
||||
.tml-sml-pad {
|
||||
padding-left: 0.05em;
|
||||
}
|
||||
.tml-med-pad {
|
||||
padding-left: 0.10em;
|
||||
}
|
||||
.tml-lrg-pad {
|
||||
padding-left: 0.15em;
|
||||
}
|
||||
}
|
||||
|
||||
@supports (-webkit-backdrop-filter: blur(1px)) {
|
||||
/* WebKit vertical & italic correction on accents */
|
||||
.wbk-acc {
|
||||
/* lower by x-height distance */
|
||||
transform: translate(0em, 0.431em);
|
||||
}
|
||||
.wbk-sml {
|
||||
transform: translate(0.07em, 0);
|
||||
}
|
||||
.wbk-sml-acc {
|
||||
transform: translate(0.07em, 0.431em);
|
||||
}
|
||||
.wbk-sml-vec {
|
||||
transform: scale(0.75) translate(0.07em, 0);
|
||||
}
|
||||
.wbk-med {
|
||||
transform: translate(0.14em, 0);
|
||||
}
|
||||
.wbk-med-acc {
|
||||
transform: translate(0.14em, 0.431em);
|
||||
}
|
||||
.wbk-med-vec {
|
||||
transform: scale(0.75) translate(0.14em, 0);
|
||||
}
|
||||
.wbk-lrg {
|
||||
transform: translate(0.21em, 0);
|
||||
}
|
||||
.wbk-lrg-acc {
|
||||
transform: translate(0.21em, 0.431em);
|
||||
}
|
||||
.wbk-lrg-vec {
|
||||
transform: scale(0.75) translate(0.21em, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/* \cancel & \phase use background images. Get them to print. */
|
||||
menclose {
|
||||
-webkit-print-color-adjust: exact; /* Chrome & Edge */
|
||||
print-color-adjust: exact;
|
||||
}
|
||||
|
||||
/* Array cell justification in Firefox & WebKit */
|
||||
.tml-right {
|
||||
text-align: right;
|
||||
}
|
||||
.tml-left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* For CD labels that grow to the left in Firefox and WebKit */
|
||||
.tml-shift-left { margin-left:-200% }
|
||||
|
||||
/* Styles for Chromium only */
|
||||
@supports (not (-webkit-backdrop-filter: blur(1px))) and (not (-moz-appearance: none)) {
|
||||
/* Italic correction on accents */
|
||||
.chr-sml {
|
||||
transform: translate(0.07em, 0)
|
||||
}
|
||||
.chr-sml-vec {
|
||||
transform: scale(0.75) translate(0.07em, 0)
|
||||
}
|
||||
.chr-med {
|
||||
transform: translate(0.14em, 0)
|
||||
}
|
||||
.chr-med-vec {
|
||||
transform: scale(0.75) translate(0.14em, 0)
|
||||
}
|
||||
.chr-lrg {
|
||||
transform: translate(0.21em, 0)
|
||||
}
|
||||
.chr-lrg-vec {
|
||||
transform: scale(0.75) translate(0.21em, 0)
|
||||
}
|
||||
|
||||
/* For CD labels that grow to the left */
|
||||
.tml-shift-left { margin-left:-100% }
|
||||
|
||||
/* MathML Core & Chromium do not support the MathML 3.0 element <menclose> attributes. */
|
||||
/* So use styles. */
|
||||
menclose {
|
||||
position: relative;
|
||||
padding: 0.5ex 0ex;
|
||||
}
|
||||
|
||||
.tml-overline {
|
||||
padding: 0.1em 0 0 0;
|
||||
border-top: 0.065em solid;
|
||||
}
|
||||
|
||||
.tml-underline {
|
||||
padding: 0 0 0.1em 0;
|
||||
border-bottom: 0.065em solid;
|
||||
}
|
||||
|
||||
.tml-cancel {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
left: 0.5px;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: currentColor;
|
||||
}
|
||||
.upstrike {
|
||||
clip-path: polygon(0.05em 100%, 0em calc(100% - 0.05em), calc(100% - 0.05em) 0em, 100% 0.05em);
|
||||
}
|
||||
.downstrike {
|
||||
clip-path: polygon(0em 0.05em, 0.05em 0em, 100% calc(100% - 0.05em), calc(100% - 0.05em) 100%);
|
||||
}
|
||||
.sout {
|
||||
clip-path: polygon(0em calc(55% + 0.0333em), 0em calc(55% - 0.0333em), 100% calc(55% - 0.0333em), 100% calc(55% + 0.0333em));
|
||||
}
|
||||
.tml-xcancel {
|
||||
background: linear-gradient(to top left,
|
||||
rgba(0,0,0,0) 0%,
|
||||
rgba(0,0,0,0) calc(50% - 0.06em),
|
||||
rgba(0,0,0,1) 50%,
|
||||
rgba(0,0,0,0) calc(50% + 0.06em),
|
||||
rgba(0,0,0,0) 100%),
|
||||
linear-gradient(to top right,
|
||||
rgba(0,0,0,0) 0%,
|
||||
rgba(0,0,0,0) calc(50% - 0.06em),
|
||||
rgba(0,0,0,1) 50%,
|
||||
rgba(0,0,0,0) calc(50% + 0.06em),
|
||||
rgba(0,0,0,0) 100%)
|
||||
}
|
||||
|
||||
.longdiv-top {
|
||||
border-top: 0.067em solid;
|
||||
padding: 0.1em 0.2em 0.2em 0.433em;
|
||||
}
|
||||
.longdiv-arc {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0.1em;
|
||||
left: -0.4em;
|
||||
width: 0.7em;
|
||||
border: 0.067em solid;
|
||||
transform: translateY(-0.067em);
|
||||
border-radius: 70%;
|
||||
clip-path: inset(0 0 0 0.4em);
|
||||
box-sizing: border-box;}
|
||||
.menclose {display: inline-block;
|
||||
text-align: left;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.phasor-bottom {
|
||||
border-bottom: 0.067em solid;
|
||||
padding: 0.2em 0.2em 0.1em 0.6em;
|
||||
}
|
||||
.phasor-angle {
|
||||
display: inline-block;
|
||||
position: absolute;
|
||||
left: 0.5px;
|
||||
bottom: -0.04em;
|
||||
height: 100%;
|
||||
aspect-ratio: 0.5;
|
||||
background-color: currentColor;
|
||||
clip-path: polygon(0.05em 100%, 0em calc(100% - 0.05em), calc(100% - 0.05em) 0em, 100% 0.05em);
|
||||
}
|
||||
|
||||
.tml-fbox {
|
||||
padding: 3pt;
|
||||
border: 1px solid;
|
||||
}
|
||||
|
||||
.circle-pad {
|
||||
padding: 0.267em;
|
||||
}
|
||||
.textcircle {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
border: 0.067em solid;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.actuarial {
|
||||
padding: 0.03889em 0.03889em 0 0.03889em;
|
||||
border-width: 0.08em 0.08em 0em 0em;
|
||||
border-style: solid;
|
||||
margin-right: 0.03889em;
|
||||
}
|
||||
|
||||
/* Stretch \widetilde */
|
||||
.tml-crooked-2 {
|
||||
transform: scale(2.0, 1.1)
|
||||
}
|
||||
.tml-crooked-3 {
|
||||
transform: scale(3.0, 1.3)
|
||||
}
|
||||
.tml-crooked-4 {
|
||||
transform: scale(4.0, 1.4)
|
||||
}
|
||||
/* set array cell justification */
|
||||
.tml-right {
|
||||
text-align: -webkit-right;
|
||||
}
|
||||
.tml-left {
|
||||
text-align: -webkit-left;
|
||||
}
|
||||
}
|
||||
|
||||
.special-fraction {
|
||||
font-family: "IBM Plex Math", 'STIX TWO', 'Times New Roman', Times, Tinos, serif;
|
||||
}
|
||||
|
||||
/* flex-wrap for line-breaking in Chromium */
|
||||
math {
|
||||
display: inline-flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: baseline;
|
||||
}
|
||||
math > mrow {
|
||||
padding: 0.5ex 0ex;
|
||||
}
|
||||
|
||||
/* Default mtd top padding is 0.5ex per MathML-Core and user-agent CSS */
|
||||
/* We adjust for jot and small */
|
||||
mtable.tml-jot mtd {
|
||||
padding-top: 0.7ex;
|
||||
padding-bottom: 0.7ex;
|
||||
}
|
||||
mtable.tml-small mtd {
|
||||
padding-top: 0.35ex;
|
||||
padding-bottom: 0.35ex;
|
||||
}
|
||||
|
||||
/* Firefox */
|
||||
@-moz-document url-prefix() {
|
||||
/* Avoid flex-wrap */
|
||||
math { display: inline; }
|
||||
math > mrow { padding: 0 }
|
||||
/* Adjust Firefox spacing between array rows */
|
||||
mtd, mtable.tml-small mtd { padding-top: 0; padding-bottom: 0; }
|
||||
mtable.tml-jot mtd { padding-top: 0.2ex; padding-bottom: 0.ex; }
|
||||
}
|
||||
|
||||
/* AMS environment auto-numbering via CSS counter. */
|
||||
.tml-eqn::before {
|
||||
counter-increment: tmlEqnNo;
|
||||
content: "(" counter(tmlEqnNo) ")";
|
||||
}
|
||||
|
||||
body {
|
||||
counter-reset: tmlEqnNo;
|
||||
}
|
||||
@@ -141,6 +141,7 @@
|
||||
\newcommand{\definedto}{≔}
|
||||
\newcommand{\equivto}{\simeq}
|
||||
\newcommand{\homotopicto}{\sim}
|
||||
\newcommand{\homotopyto}{\sim}
|
||||
\newcommand{\naturalto}{\Rightarrow}
|
||||
\newcommand{\isoto}{\cong}
|
||||
\newcommand{\monicto}{\rightarrowtail}
|
||||
|
||||
11
doerg/resources/net/deertopia/doerg/preview-template.tex
Normal file
11
doerg/resources/net/deertopia/doerg/preview-template.tex
Normal file
@@ -0,0 +1,11 @@
|
||||
\documentclass{article}
|
||||
\usepackage{amsmath}
|
||||
\usepackage[active,tightpage,auctex,dvips]{preview}
|
||||
\usepackage{fontspec}
|
||||
\usepackage{ifxetex}
|
||||
\usepackage{syd-plex}
|
||||
|
||||
\begin{document}
|
||||
\setlength\abovedisplayskip{0pt}
|
||||
% {{contents}}
|
||||
\end{document}
|
||||
@@ -542,3 +542,7 @@ figure.fullwidth figcaption {
|
||||
; max-width: 55%
|
||||
; font-size: 1.5rem
|
||||
}
|
||||
|
||||
.latex-fragment
|
||||
{ fill: currentColor
|
||||
}
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
(ns net.deertopia.doerg.common
|
||||
(:require [babashka.process :as p]
|
||||
[clojure.string :as str]))
|
||||
[clojure.string :as str]
|
||||
[clojure.tools.logging :as l]
|
||||
[clojure.java.io :as io])
|
||||
(:import (java.io FilterInputStream StringWriter InputStream
|
||||
OutputStream PrintStream ByteArrayOutputStream
|
||||
ByteArrayInputStream FilterOutputStream)
|
||||
(java.nio.charset StandardCharsets)))
|
||||
|
||||
(defn deref-with-timeout [process ms]
|
||||
(let [p (promise)
|
||||
@@ -16,3 +22,104 @@
|
||||
{:process process
|
||||
:timed-out-after-milliseconds ms}))
|
||||
@p)))
|
||||
|
||||
(defn tee-input-stream
|
||||
"Return a wrapped `InputStream` that writes all bytes read from
|
||||
input-stream to sink, à la the UNIX command tee(1)."
|
||||
[input-stream sink]
|
||||
(proxy [FilterInputStream] [input-stream]
|
||||
(read
|
||||
([]
|
||||
(let [c (proxy-super read)]
|
||||
(when (not= c -1)
|
||||
(.write sink c))
|
||||
c))
|
||||
([^bytes bs]
|
||||
(let [n (proxy-super read bs)]
|
||||
(when (not= n -1)
|
||||
(.write sink bs 0 n))
|
||||
n))
|
||||
([^bytes bs off len]
|
||||
(let [n (proxy-super read bs off len)]
|
||||
(when (not= n -1)
|
||||
(.write sink bs off n))
|
||||
n)))
|
||||
(close []
|
||||
(try (proxy-super close)
|
||||
(finally (.close sink))))))
|
||||
|
||||
(defn tee-output-stream
|
||||
"Return a wrapped `OutputStream` that writes all bytes written to
|
||||
output-stream to sink, à la the UNIX command tee(1)."
|
||||
[output-stream sink]
|
||||
(proxy [FilterOutputStream] [output-stream]
|
||||
(write
|
||||
([bs-or-b]
|
||||
(proxy-super write bs-or-b)
|
||||
(.write sink bs-or-b))
|
||||
([^bytes bs off len]
|
||||
(proxy-super write bs off len)
|
||||
(.write sink bs off len)))
|
||||
(close []
|
||||
(try (proxy-super close)
|
||||
(finally (.close sink))))))
|
||||
|
||||
#_
|
||||
(defn hook-input-stream [input-stream hook]
|
||||
(proxy [FilterInputStream] [input-stream]
|
||||
(read
|
||||
([]
|
||||
(let [c (proxy-super read)]
|
||||
(when (not= c -1)
|
||||
(hook (byte-array [c])))
|
||||
c))
|
||||
([^bytes bs]
|
||||
(let [n (proxy-super read bs)]
|
||||
(when (not= n -1)
|
||||
(let [bs* (byte-array n 0)]
|
||||
(System/arraycopy bs 0 bs* 0 n)
|
||||
(hook bs*)))
|
||||
n))
|
||||
([^bytes bs off len]
|
||||
(let [n (proxy-super read bs off len)]
|
||||
(when (not= n -1)
|
||||
(.write sink bs off n))
|
||||
n)))
|
||||
(close []
|
||||
(try (proxy-super close)
|
||||
(finally (.close sink))))))
|
||||
|
||||
(comment
|
||||
(with-open [sink (ByteArrayOutputStream.)
|
||||
out (ByteArrayOutputStream.)
|
||||
in (ByteArrayInputStream. (.getBytes "hello worms"))]
|
||||
(io/copy (tee-input-stream in sink) out)
|
||||
(def the-out out)
|
||||
(def the-sink sink)
|
||||
{:out out
|
||||
:sink sink})
|
||||
(with-open [sink (l/log-stream :info "blah")
|
||||
out (ByteArrayOutputStream.)
|
||||
in (ByteArrayInputStream. (.getBytes "hello worms"))]
|
||||
(io/copy (tee-input-stream in sink) out)
|
||||
(def the-out out)
|
||||
(def the-sink sink)
|
||||
{:out out
|
||||
:sink sink}))
|
||||
|
||||
(comment
|
||||
(let [out (ByteArrayOutputStream.)]
|
||||
(p/shell {:out (tee-output-stream
|
||||
out (l/log-stream :info "blah"))}
|
||||
"echo" "hello\n" "worms")
|
||||
(.toString out)))
|
||||
|
||||
(defn invoke [opts & cmd]
|
||||
(l/info (str/join " " (cons "$" cmd)))
|
||||
(let [r (apply p/shell
|
||||
(merge {:continue true
|
||||
:in nil :out :string :err :string}
|
||||
opts)
|
||||
cmd)
|
||||
bin (first cmd)]
|
||||
r))
|
||||
|
||||
@@ -180,8 +180,8 @@
|
||||
parser, return a map with the following keys
|
||||
• :top-level-nodes The nodes that /should/ be at the top-level.
|
||||
• :first-section-nodes The nodes that should be wrapped in a new
|
||||
section node
|
||||
• :rest Everything else!"
|
||||
section node.
|
||||
• :rest Everything else."
|
||||
[nodes]
|
||||
(let [[of-top-level remaining-nodes]
|
||||
(->> nodes (split-with #(of-type? % "property-drawer" "keyword")))
|
||||
|
||||
@@ -31,28 +31,17 @@
|
||||
[:link {:rel "stylesheet" :type "text/css" :href href}])
|
||||
|
||||
(def ibm-plex
|
||||
(for [family ["serif" "sans-kr" "math"]]
|
||||
#_
|
||||
[:style (-> cfg/*cfg* ::cfg/ibm-plex-web
|
||||
(fs/file (format "css/ibm-plex-%s-default.min.css" family))
|
||||
slurp)]
|
||||
(external-stylesheet
|
||||
(format "ibm-plex-web/css/ibm-plex-%s-all.min.css" family))))
|
||||
(concat
|
||||
(for [family ["serif" "sans-kr" "math"]]
|
||||
(external-stylesheet
|
||||
(format "ibm-plex-web/css/ibm-plex-%s-all.min.css" family)))
|
||||
[(external-stylesheet "Temml-Plex.css")]))
|
||||
|
||||
(def deerstar
|
||||
(external-stylesheet "deerstar.css")
|
||||
#_
|
||||
[:style (slurp (io/resource "net/deertopia/doerg/deerstar.css"))])
|
||||
(external-stylesheet "deerstar.css"))
|
||||
|
||||
(def tuftesque
|
||||
(external-stylesheet "tuftesque.css")
|
||||
#_
|
||||
[:link {:rel "stylesheet"
|
||||
:type "text/css"
|
||||
:href "/resources/tuftesque.css"}]
|
||||
#_
|
||||
[:style
|
||||
(slurp (io/resource "net/deertopia/doerg/tuftesque.css"))])
|
||||
(external-stylesheet "tuftesque.css"))
|
||||
|
||||
(def head
|
||||
(list viewport charset ibm-plex deerstar tuftesque))
|
||||
|
||||
@@ -9,8 +9,12 @@
|
||||
[net.deertopia.doerg.html :as doerg-html]
|
||||
[hiccup2.core :as hiccup]
|
||||
[clojure.pprint]
|
||||
#_
|
||||
[net.deertopia.doerg.tex :as tex]
|
||||
[clojure.zip :as z]))
|
||||
[net.deertopia.doerg.tex.native :as tex-native]
|
||||
[net.deertopia.doerg.tex.temml :as tex-temml]
|
||||
[clojure.zip :as z]
|
||||
[babashka.fs :as fs]))
|
||||
|
||||
;;; Top-level API
|
||||
|
||||
@@ -40,33 +44,39 @@
|
||||
(def ^:dynamic ^:private *document-info*)
|
||||
|
||||
(declare ^:private gather-footnotes render-renderer-error
|
||||
view-children-as-seq)
|
||||
view-children-as-seq render-tex-snippets)
|
||||
|
||||
(defn org-element-recursive
|
||||
"Recursively render an Org-mode element to Hiccup."
|
||||
[e]
|
||||
(tex/binding-tex-worker
|
||||
(->> e
|
||||
;; gather-footnotes
|
||||
(sp/transform
|
||||
[element/postorder-walker view-children-as-seq]
|
||||
(fn [node]
|
||||
(try (org-element node)
|
||||
(catch Throwable e
|
||||
(lr/error e "Error in renderer" {:node node})
|
||||
(render-renderer-error e))))))))
|
||||
(->> e
|
||||
(sp/transform
|
||||
[element/postorder-walker view-children-as-seq]
|
||||
(fn [node]
|
||||
(try (org-element node)
|
||||
(catch Throwable e
|
||||
(lr/error e "Error in renderer" {:node node})
|
||||
(render-renderer-error e)))))))
|
||||
|
||||
(defn org-document
|
||||
"Recursively render an Org-mode document to Hiccup."
|
||||
[doc]
|
||||
(let [rendered (org-element-recursive (gather-footnotes doc))]
|
||||
[:html
|
||||
[:head
|
||||
[:title "org document"]
|
||||
doerg-html/head]
|
||||
[:body
|
||||
[:article
|
||||
rendered]]]))
|
||||
(tex-temml/binding-worker
|
||||
(let [rendered (-> doc gather-footnotes render-tex-snippets
|
||||
org-element-recursive)]
|
||||
[:html
|
||||
[:head
|
||||
[:title "org document"]
|
||||
doerg-html/head]
|
||||
[:body
|
||||
[:article
|
||||
rendered]]])))
|
||||
|
||||
(defn to-html
|
||||
"Read `f` with `slurp` as an Org document and return a string of
|
||||
rendered HTML."
|
||||
[f]
|
||||
(str (hiccup/html {} (-> f slurp element/read-string org-document))))
|
||||
|
||||
|
||||
;;; Further dispatching on `org-element`
|
||||
@@ -90,17 +100,6 @@
|
||||
(sp/view #(update % :children seq))
|
||||
sp/STAY))
|
||||
|
||||
#_
|
||||
(defn- gather-footnotes [doc]
|
||||
(->> doc
|
||||
(sp/select
|
||||
[element/children-walker element/footnotes-section?
|
||||
element/children-walker
|
||||
#(element/of-type? % "footnote-definition")
|
||||
(sp/view (fn [d]
|
||||
{(:label d) d}))])
|
||||
(apply merge)))
|
||||
|
||||
(defn- contains-footnote-refs? [node]
|
||||
(some #(element/of-type? % "footnote-reference")
|
||||
(:children node)))
|
||||
@@ -137,6 +136,88 @@
|
||||
element/footnotes-section?]
|
||||
sp/NONE))))
|
||||
|
||||
(defn- collect-latex-headers [doc]
|
||||
(->> doc
|
||||
(sp/select
|
||||
[element/postorder-walker
|
||||
#(element/of-keyword-type? % "LATEX_HEADER")
|
||||
(sp/view :value)])))
|
||||
|
||||
(defn- read-and-patch-generated-svg [{:keys [file height depth]}]
|
||||
;; dvisvgm writes standalone SVG files, to which we need to make a
|
||||
;; few changes to use them inline within our HTML.
|
||||
;; • XML header: Bad syntax when embedded in an HTML doc. Remove
|
||||
;; it.
|
||||
;; • Width and height: We override these with our own values
|
||||
;; computed by `net.deertopia.doerg.tex` to ensure correct
|
||||
;; positioning relative to the surrounding text. More
|
||||
;; accurately, we remove the height and width attributes from
|
||||
;; the SVG tag, and set the new values for height and
|
||||
;; vertical-align in the style attribute
|
||||
;; • Viewbox: Must be removed entirely for correct positioning.
|
||||
(-> (slurp file)
|
||||
(str/replace-first #"<\?xml version='1.0' encoding='UTF-8'\?>\n?" "")
|
||||
(str/replace-first #" height=['\"][^\"']+[\"']" "")
|
||||
(str/replace-first #" width=['\"][^\"']+[\"']" "")
|
||||
(str/replace-first
|
||||
#"viewBox=['\"][^\"']+[\"']"
|
||||
(fn [s]
|
||||
(format "%s style=\"%s\""
|
||||
s
|
||||
(format "height:%.4fem;vertical-align:%.4fem;display:inline-block"
|
||||
height (- depth)))))))
|
||||
|
||||
(defn render-tex-snippets
|
||||
"Traverse doc, adorning each LaTeX node with a promise resolving to,
|
||||
optimistically, Hiccup-rendered SVG or MathML code."
|
||||
[doc]
|
||||
(let [promises (atom [])
|
||||
r (->> doc (sp/transform
|
||||
[element/postorder-walker
|
||||
#(element/of-type?
|
||||
% "latex-fragment" "latex-environment")]
|
||||
(fn [node]
|
||||
(let [p (promise)]
|
||||
(swap! promises #(conj % {:promise p :node node}))
|
||||
(assoc node ::rendered p)))))
|
||||
f (fn []
|
||||
(fs/with-temp-dir [svg-dir {:prefix "doerg-svg"}]
|
||||
(let [rendered-snippets
|
||||
(delay (->> @promises
|
||||
(map #(-> % :node :value))
|
||||
(apply tex-native/render svg-dir)))]
|
||||
(doseq [{:keys [promise node]} @promises]
|
||||
(try (let [{:keys [value]} node
|
||||
temml (tex-temml/render value)]
|
||||
(if (tex-temml/erroneous-output? temml)
|
||||
(let [tex (get @rendered-snippets value)]
|
||||
(if (:errors tex)
|
||||
(deliver promise (hiccup/raw temml))
|
||||
(->> tex
|
||||
read-and-patch-generated-svg
|
||||
hiccup/raw
|
||||
(deliver promise))))
|
||||
(deliver promise (hiccup/raw temml))))
|
||||
(catch Exception e
|
||||
(lr/error e)
|
||||
(throw e))))
|
||||
(when (fs/exists? "/tmp/doerg-svgs")
|
||||
(fs/delete-tree "/tmp/doerg-svgs"))
|
||||
(fs/copy-tree svg-dir "/tmp/doerg-svgs"))))
|
||||
fut (future-call (bound-fn* f))]
|
||||
;; Time out after eight seconds. With all the LaTeX and IPC, there
|
||||
;; are so many opportunities for things to go wrong </3.
|
||||
(let [fut-res (deref fut (* 10 1000) ::timed-out)]
|
||||
(if (= fut-res ::timed-out)
|
||||
(do (future-cancel fut)
|
||||
(doseq [{:keys [promise]} @promises]
|
||||
(deliver promise ::timed-out)))
|
||||
fut-res))
|
||||
r))
|
||||
|
||||
(comment
|
||||
(render-tex-snippets doc))
|
||||
|
||||
|
||||
|
||||
(defn- render-pprint
|
||||
@@ -280,15 +361,12 @@
|
||||
(str "@" key))
|
||||
|
||||
(defmethod org-element "latex-fragment" [{:keys [contents value] :as e}]
|
||||
(let [display? (str/starts-with? value "\\[")]
|
||||
#_
|
||||
(render-pprint (assoc e :display? display?))
|
||||
[:span.latex-fragment
|
||||
(hiccup/raw (tex/render contents :display? display?))]))
|
||||
|
||||
(defmethod org-element "latex-environment" [{:keys [value]}]
|
||||
[:span.latex-fragment
|
||||
(hiccup/raw (tex/render value :display? true))])
|
||||
(-> e ::rendered deref)])
|
||||
|
||||
(defmethod org-element "latex-environment" [{:keys [value] :as e}]
|
||||
[:span.latex-fragment
|
||||
(-> e ::rendered deref)])
|
||||
|
||||
(defmethod org-element "example-block" [{:keys [value]}]
|
||||
[:pre value])
|
||||
@@ -307,7 +385,6 @@
|
||||
;; Completely ignore the LATEX_COMPILER keyword.
|
||||
(defmethod org-keyword "LATEX_COMPILER" [_] nil)
|
||||
|
||||
;; TODO: Real LatEx support.
|
||||
(defmethod org-keyword "LATEX_HEADER" [_] nil)
|
||||
|
||||
;; Not sure how to deal with this one yet.
|
||||
@@ -330,4 +407,3 @@
|
||||
[:span.org-link.external
|
||||
[:a {:href raw-link}
|
||||
(or (seq children) raw-link)]])
|
||||
|
||||
|
||||
@@ -14,7 +14,11 @@
|
||||
"/home/msyds/org/20250919114912-homepage.org"
|
||||
#_
|
||||
"/home/msyds/org/20251111182118-path_induction.org"
|
||||
"/home/msyds/org/20250512144715-natural_transformation_category_theory.org")
|
||||
#_
|
||||
"/home/msyds/org/20250512144715-natural_transformation_category_theory.org"
|
||||
#_
|
||||
"/home/msyds/org/20251021155921-path_action.org"
|
||||
"test/net/deertopia/doerg/render_test/fallbacks.org")
|
||||
|
||||
(defn- force-create-sym-link [path target]
|
||||
(fs/delete-if-exists path)
|
||||
@@ -30,9 +34,10 @@
|
||||
(io/resource "net/deertopia/doerg/deerstar.css"))
|
||||
(force-create-sym-link (fs/file dest "tuftesque.css")
|
||||
(io/resource "net/deertopia/doerg/tuftesque.css"))
|
||||
(force-create-sym-link (fs/file dest "Temml-Plex.css")
|
||||
(io/resource "net/deertopia/doerg/Temml-Plex.css"))
|
||||
(fs/delete-if-exists (fs/file dest "index.html"))
|
||||
(->> (h/html (-> src slurp element/read-string render/org-document))
|
||||
str (spit (fs/file dest "index.html"))))
|
||||
(->> src render/to-html str (spit (fs/file dest "index.html"))))
|
||||
|
||||
(defn render-edn [& {:keys [src dest]
|
||||
:or {src some-org-file
|
||||
|
||||
@@ -1,49 +1,4 @@
|
||||
(ns net.deertopia.doerg.tex
|
||||
(:require [babashka.process :as p]
|
||||
[net.deertopia.doerg.common :as common]
|
||||
[clj-cbor.core :as cbor]
|
||||
[clojure.java.io :as io]))
|
||||
|
||||
(def ^:dynamic *tex-worker-timeout-duration*
|
||||
"Number of milliseconds to wait before killing the external Uniorg
|
||||
process."
|
||||
(* 10 1000))
|
||||
|
||||
(def ^:dynamic *worker*)
|
||||
|
||||
(defn tex-worker [& {:keys [preamble]}]
|
||||
(p/process
|
||||
{:shutdown p/destroy-tree
|
||||
:err :inherit}
|
||||
#_"doerg-tex"
|
||||
"./doerg-tex/index.js"
|
||||
"--preamble"
|
||||
"resources/net/deertopia/doerg/prelude.tex"))
|
||||
|
||||
(defn finish [tw]
|
||||
(.close (:in tw)))
|
||||
|
||||
(defmacro with-tex-worker [tw & body]
|
||||
`(let [~tw (tex-worker)]
|
||||
(try
|
||||
(do ~@body)
|
||||
(finally
|
||||
(finish ~tw)
|
||||
(p/destroy-tree ~tw)))))
|
||||
|
||||
(defmacro binding-tex-worker [& body]
|
||||
`(binding [*worker* (tex-worker)]
|
||||
(try
|
||||
~@body
|
||||
(finally
|
||||
(finish *worker*)))))
|
||||
|
||||
(defn command [x]
|
||||
(cbor/encode cbor/default-codec (:in *worker*) x)
|
||||
(.flush (:in *worker*))
|
||||
(cbor/decode cbor/default-codec (:out *worker*)))
|
||||
|
||||
(defn render [s & {:keys [display?]}]
|
||||
(if display?
|
||||
(command [s])
|
||||
(command s)))
|
||||
(:require [net.deertopia.doerg.tex.native :as native]
|
||||
[net.deertopia.doerg.tex.temml :as temml]
|
||||
[babashka.fs :as fs]))
|
||||
|
||||
154
doerg/src/net/deertopia/doerg/tex/native.clj
Normal file
154
doerg/src/net/deertopia/doerg/tex/native.clj
Normal file
@@ -0,0 +1,154 @@
|
||||
(ns net.deertopia.doerg.tex.native
|
||||
"Shelling out to (Xe)LaTeX and dvisvgm. Much magic borrowed from
|
||||
the org-latex-preview package for Emacs."
|
||||
(:require [babashka.process :as p]
|
||||
[net.deertopia.doerg.common :as common]
|
||||
[clojure.java.io :as io]
|
||||
[clojure.string :as str]
|
||||
[clojure.tools.logging :as l]
|
||||
[babashka.fs :as fs])
|
||||
(:import (java.io ByteArrayOutputStream)))
|
||||
|
||||
(def ^:private scale-divisor 66873.46948423679)
|
||||
|
||||
(def ^:private font-size 10)
|
||||
|
||||
(def ^:private tightpage-regexp
|
||||
#"Preview: Tightpage (-?\d+) *(-?\d+) *(-?\d+) *(-?\d+)")
|
||||
|
||||
(def ^:private preview-start-regexp
|
||||
#"! Preview: Snippet (\d+) started.")
|
||||
|
||||
(def ^:private preview-end-regexp
|
||||
#"(?:^Preview: Tightpage.*$)?\n! Preview: Snippet (\d+) ended.\((\d+)\+(\d+)x(\d+)\)")
|
||||
|
||||
(defn- invoke [extra-opts & args]
|
||||
(let [namespace (or (::ns extra-opts) (first args))
|
||||
out-bytes (ByteArrayOutputStream.)
|
||||
out-stream (common/tee-output-stream
|
||||
out-bytes
|
||||
(l/log-stream :info (str namespace "/out")))
|
||||
err-stream (l/log-stream :info (str namespace "/err"))
|
||||
opts (merge extra-opts
|
||||
{:out out-stream :err err-stream :continue true
|
||||
:shutdown p/destroy-tree
|
||||
:pre-start-fn (fn [{:keys [cmd]}]
|
||||
(l/infof "$ %s"
|
||||
(str/join " " cmd)))
|
||||
:exit-fn (fn [{:keys [cmd exit]}]
|
||||
(l/infof "%s exited w/ status %d"
|
||||
(first cmd) exit))})
|
||||
r (apply p/shell opts args)
|
||||
out (.toString out-bytes)]
|
||||
(-> r
|
||||
(assoc ::out out))))
|
||||
|
||||
(defn- parse-tightpage [latex-out]
|
||||
(->> (re-find tightpage-regexp latex-out)
|
||||
(drop 1)
|
||||
(map parse-long)))
|
||||
|
||||
(defn- compute-geometry [[tp1 tp2 tp3 tp4] [d1 d2 d3]]
|
||||
(let [depth (/ (- d2 tp2) scale-divisor font-size)]
|
||||
{:depth depth
|
||||
:height (+ depth
|
||||
(/ (+ d1 tp4)
|
||||
scale-divisor
|
||||
font-size))
|
||||
:width (/ (+ d3 tp3 (- tp2))
|
||||
scale-divisor
|
||||
font-size)}))
|
||||
|
||||
(defn- parse-latex-output [out]
|
||||
(let [tightpage-info (parse-tightpage out)
|
||||
m-start (re-matcher preview-start-regexp out)
|
||||
m-end (re-matcher preview-end-regexp out)]
|
||||
(loop [acc []]
|
||||
(if-some [[_ snippet-ix] (re-find m-start)]
|
||||
(let [r (re-find m-end)
|
||||
[_ snippet-ix* _ _ _] r
|
||||
dimensional-info (->> r (drop 2) (map parse-long))
|
||||
errors (-> out
|
||||
(subs (.end m-start) (.start m-end))
|
||||
(str/replace-first #"[^!]*" "")
|
||||
str/trim)]
|
||||
(assert (= snippet-ix snippet-ix*))
|
||||
(recur (conj acc (-> (compute-geometry
|
||||
tightpage-info dimensional-info)
|
||||
(assoc :errors (if (empty? errors)
|
||||
nil
|
||||
errors))))))
|
||||
acc))))
|
||||
|
||||
(defn- invoke-latex [& {:keys [file output-dir latex]
|
||||
:or {latex "xelatex"}}]
|
||||
(invoke
|
||||
{:dir output-dir}
|
||||
latex "-no-pdf" "-interaction" "nonstopmode"
|
||||
"-output-directory" output-dir file))
|
||||
|
||||
(defn- invoke-dvisvgm [& {:keys [file output-dir]}]
|
||||
(invoke
|
||||
{:dir output-dir}
|
||||
"dvisvgm" "--page=1-" "--optimize" "--clipjoin"
|
||||
"--relative" "--no-fonts" "-v3"
|
||||
"--message=processing page {?pageno}: output written to {?svgpath}"
|
||||
"--bbox=preview" "-o" "%9p.svg" file))
|
||||
|
||||
(defn- snippet-file-names
|
||||
"Return a map of TeX snippets (as strings, including the math
|
||||
delimiters) to file names as would be output by
|
||||
`invoke-dvisvgm`. The returned file names are relative to dvisvgm's
|
||||
output directory."
|
||||
[snippets]
|
||||
(let [svgs (for [i (range)]
|
||||
(format "%09d.svg" i))]
|
||||
(zipmap (reverse snippets) svgs)))
|
||||
|
||||
(defn- instantiate-preview-template [snippets]
|
||||
(let [contents (->> (for [s snippets]
|
||||
(format "\\begin{preview}\n%s\n\\end{preview}" s))
|
||||
(str/join "\n"))]
|
||||
(-> (io/resource "net/deertopia/doerg/preview-template.tex")
|
||||
slurp
|
||||
(str/replace-first "% {{contents}}" contents))))
|
||||
|
||||
(defn render
|
||||
"Render a collection of `snippets` to SVGs in `output-dir` using a
|
||||
LaTeX engine (XeLaTeX at the moment) and dvisvgm. Returns a map
|
||||
whose keys are `snippets` and whose values are maps containing
|
||||
geometry info, a string of errors output by LaTeX, and the path to
|
||||
the generated SVG file. Math delimiters are *not* implicitly added
|
||||
to each snippet."
|
||||
[output-dir & snippets]
|
||||
(fs/with-temp-dir [dir {:prefix "doerg-latex"}]
|
||||
(let [preview-tex (fs/file dir "preview.tex")
|
||||
preview-xdv (fs/file dir "preview.xdv")
|
||||
distinct-snippets (distinct snippets)]
|
||||
(fs/create-dirs output-dir)
|
||||
(->> (instantiate-preview-template distinct-snippets)
|
||||
(spit preview-tex))
|
||||
(let [dimensions (-> (invoke-latex :output-dir dir :file preview-tex)
|
||||
::out parse-latex-output)
|
||||
_ (invoke-dvisvgm :output-dir output-dir :file preview-xdv)]
|
||||
;; Adorn each snippet with dimensions and errors parsed from
|
||||
;; LaTeX's output, and the paths to SVG files generated by
|
||||
;; dvisvgm.
|
||||
(assert (= (count distinct-snippets) (count dimensions)))
|
||||
(->> (map (fn [ix snippet dimensions]
|
||||
{snippet
|
||||
(-> dimensions
|
||||
(assoc
|
||||
:file (fs/file output-dir
|
||||
(format "%09d.svg" (inc ix)))))})
|
||||
(range)
|
||||
distinct-snippets
|
||||
dimensions)
|
||||
(into {}))))))
|
||||
|
||||
(comment
|
||||
(render "/tmp/doerg-tex-svgs"
|
||||
"\\(c = \\sqrt{x^2 + y^2}\\)"
|
||||
"\\(x\\)" "\\(y\\)" "\\(x\\)"
|
||||
"\\(\\undefinedcommandlol\\)"))
|
||||
|
||||
67
doerg/src/net/deertopia/doerg/tex/temml.clj
Normal file
67
doerg/src/net/deertopia/doerg/tex/temml.clj
Normal file
@@ -0,0 +1,67 @@
|
||||
(ns net.deertopia.doerg.tex.temml
|
||||
(:require [babashka.process :as p]
|
||||
[net.deertopia.doerg.common :as common]
|
||||
[clj-cbor.core :as cbor]
|
||||
[clojure.java.io :as io]
|
||||
[clojure.string :as str]
|
||||
[clojure.tools.logging :as l]
|
||||
[babashka.fs :as fs])
|
||||
(:import (java.io ByteArrayOutputStream)))
|
||||
|
||||
(def ^:dynamic *worker-timeout-duration*
|
||||
"Number of milliseconds to wait before killing the external Uniorg
|
||||
process."
|
||||
(* 10 1000))
|
||||
|
||||
(def ^:dynamic *worker*)
|
||||
|
||||
(defn worker [& {:keys [preamble]}]
|
||||
(p/process
|
||||
{:shutdown p/destroy-tree
|
||||
:err (l/log-stream :info "temml/err")}
|
||||
#_"doerg-tex"
|
||||
"./doerg-tex/index.js"
|
||||
"--preamble"
|
||||
"resources/net/deertopia/doerg/prelude.tex"))
|
||||
|
||||
(defn close-worker [tw]
|
||||
(.close (:in tw)))
|
||||
|
||||
(defmacro with-worker [tw & body]
|
||||
`(let [~tw (worker)]
|
||||
(try
|
||||
(do ~@body)
|
||||
(finally
|
||||
(close-worker ~tw)
|
||||
(p/destroy-tree ~tw)))))
|
||||
|
||||
(defmacro binding-worker [& body]
|
||||
`(binding [*worker* (worker)]
|
||||
(try
|
||||
~@body
|
||||
(finally
|
||||
(close-worker *worker*)))))
|
||||
|
||||
(defn command-worker [x]
|
||||
(cbor/encode cbor/default-codec (:in *worker*) x)
|
||||
(.flush (:in *worker*))
|
||||
(cbor/decode cbor/default-codec (:out *worker*)))
|
||||
|
||||
(defn render-inline [s]
|
||||
(command-worker s))
|
||||
|
||||
(defn render-display [s]
|
||||
(command-worker [s]))
|
||||
|
||||
(defn render [s]
|
||||
(if-let [[_ inner] (re-matches #"(?s)\\[(.*)\\]" s)]
|
||||
(render-display inner)
|
||||
(if (re-matches #"(?s)\\begin\{.+?}(.*?)\\end\{.+?}" s)
|
||||
(render-display s)
|
||||
(if-let [[_ inner] (re-matches #"(?s)\\\((.*)\\\)" s)]
|
||||
(render-inline inner)
|
||||
(throw (ex-info "weird" {:snippet s}))))))
|
||||
|
||||
;; hackky....
|
||||
(defn erroneous-output? [s]
|
||||
(re-find #"(#b22222|temml-error)" s))
|
||||
60
doerg/test/net/deertopia/doerg/render_test.clj
Normal file
60
doerg/test/net/deertopia/doerg/render_test.clj
Normal file
@@ -0,0 +1,60 @@
|
||||
(ns net.deertopia.doerg.render-test
|
||||
(:require [net.deertopia.doerg.render :as sut]
|
||||
[net.deertopia.doerg.element :as element]
|
||||
[net.deertopia.doerg.tex.temml :as temml]
|
||||
[net.deertopia.doerg.tex.native :as native]
|
||||
[com.rpl.specter :as sp]
|
||||
[clojure.edn :as edn]
|
||||
[clojure.test :as t]
|
||||
[clojure.java.io :as io]
|
||||
[clojure.string :as str]))
|
||||
|
||||
;; Stupid and hacky.
|
||||
(defn mathml? [s]
|
||||
(str/starts-with? s "<math"))
|
||||
|
||||
;; Also stupid and hacky. }:)
|
||||
(defn svg? [s]
|
||||
(some? (re-matches #"(?s)(<!--.*?-->\n)<svg.*" s)))
|
||||
|
||||
(defn read-resource [s]
|
||||
(let [p (-> (format "net/deertopia/doerg/render_test/%s" s)
|
||||
io/resource slurp)]
|
||||
(cond (str/ends-with? s ".edn") (edn/read-string p)
|
||||
(str/ends-with? s ".org") (element/read-string p))))
|
||||
|
||||
(t/deftest latex-fallbacks
|
||||
(t/testing "LaTeX fallback behaviour"
|
||||
(let [doc (temml/binding-worker
|
||||
(-> "fallbacks.org" read-resource sut/render-tex-snippets))
|
||||
snippets (->> doc
|
||||
(sp/select
|
||||
[element/postorder-walker
|
||||
#(element/of-type?
|
||||
% "latex-fragment" "latex-environment")
|
||||
(sp/view #(-> % ::sut/rendered deref str))]))
|
||||
expectations (-> "fallbacks.edn" read-resource)]
|
||||
(doall (map (fn [s e]
|
||||
(let [mathml (mathml? s)
|
||||
svg (svg? s)]
|
||||
(assert
|
||||
(not= mathml svg)
|
||||
"`mathml?` and `svg?` should be mutually-exclusive.")
|
||||
(case e
|
||||
:mathml (t/is mathml)
|
||||
:svg (t/is svg))))
|
||||
snippets expectations)))))
|
||||
|
||||
(t/deftest latex-laziness
|
||||
(t/testing "LaTeX laziness"
|
||||
(let [ex (Exception. "you're supposed to be lazy!")
|
||||
bad (fn [& _] (throw ex))
|
||||
doc (read-resource "latexless.org")
|
||||
r (try (with-redefs-fn {#'native/render bad
|
||||
#'temml/render bad}
|
||||
#(sut/render-tex-snippets doc))
|
||||
(catch Exception e
|
||||
(if (= e ex)
|
||||
false
|
||||
(throw e))))]
|
||||
(t/is r))))
|
||||
7
doerg/test/net/deertopia/doerg/render_test/fallbacks.edn
Normal file
7
doerg/test/net/deertopia/doerg/render_test/fallbacks.edn
Normal file
@@ -0,0 +1,7 @@
|
||||
[:mathml
|
||||
:mathml
|
||||
:mathml
|
||||
:mathml
|
||||
:svg
|
||||
:mathml
|
||||
:svg]
|
||||
20
doerg/test/net/deertopia/doerg/render_test/fallbacks.org
Normal file
20
doerg/test/net/deertopia/doerg/render_test/fallbacks.org
Normal file
@@ -0,0 +1,20 @@
|
||||
#+title: aghhh
|
||||
|
||||
- blah blah prose prose prose \(c = \sqrt{x^2 + y^2}\), alal.
|
||||
- this thing is \(x\)
|
||||
- another thing \(y\)
|
||||
- this thing is also \(x\) and uses the same svg
|
||||
- ifxetex: \(\ifxetex alalala\fi \)
|
||||
|
||||
balahahahahahaj
|
||||
\begin{align*}
|
||||
x &= y
|
||||
\\ &= zzz.
|
||||
\end{align*}
|
||||
awawawa
|
||||
|
||||
cool ass tbale
|
||||
\begin{tabular}{|c|c|c|}
|
||||
blah & glah & zlah
|
||||
\\ abdwa & www &dj
|
||||
\end{tabular}
|
||||
3
doerg/test/net/deertopia/doerg/render_test/latexless.org
Normal file
3
doerg/test/net/deertopia/doerg/render_test/latexless.org
Normal file
@@ -0,0 +1,3 @@
|
||||
#+title: 이 파일은 LaTeX 코드가 포함되지 않습니다.
|
||||
|
||||
🦌!
|
||||
Reference in New Issue
Block a user