feat: tex rendering via temml

This commit is contained in:
2026-02-16 03:55:07 -07:00
parent a26df4011a
commit 651ed4f26c
19 changed files with 690 additions and 60 deletions

View File

@@ -0,0 +1,18 @@
(ns net.deertopia.doerg.common
(:require [babashka.process :as p]
[clojure.string :as str]))
(defn deref-with-timeout [process ms]
(let [p (promise)
process-future (future (deliver p @process))
timeout-future (future (Thread/sleep ms)
(future-cancel process-future)
(p/destroy-tree process)
(deliver p ::timed-out))]
(if (= @p ::timed-out)
(throw (ex-info (format "external command `%s' timed out after %.2fs."
(str/join " " (:cmd process))
(/ (double ms) 1000))
{:process process
:timed-out-after-milliseconds ms}))
@p)))

View File

@@ -1,5 +1,6 @@
(ns net.deertopia.doerg.element
(:require [babashka.process :as p]
[net.deertopia.doerg.common :as common]
[clojure.string :as str]
[clojure.zip]
[babashka.fs :as fs]
@@ -16,24 +17,11 @@
(defonce ^:private uniorg-script-path-atom (atom nil))
(def ^:dynamic *uniorg-timeout-after-milliseconds*
(def ^:dynamic *uniorg-timeout-duration*
"Number of milliseconds to wait before killing the external Uniorg
process."
(* 10 1000))
(defn deref-with-timeout [process ms]
(let [p (promise)
process-future (future (deliver p @process))
timeout-future (future (Thread/sleep ms)
(future-cancel process-future)
(p/destroy-tree process)
(deliver p ::timed-out))]
(if (= @p ::timed-out)
(throw (ex-info (format "external command `%s' timed out after %.2fs."
(str/join " " (:cmd process))
(/ (double ms) 1000))
{:process process
:timed-out-after-milliseconds ms}))
@p)))
(defn- camel->kebab [s]
(->> (str/split s #"(?<=[a-z])(?=[A-Z])")
(map str/lower-case)
@@ -44,7 +32,7 @@
(let [r (-> (p/process
{:in in :out :string}
"doerg-parser")
(deref-with-timeout *uniorg-timeout-after-milliseconds*))]
(common/deref-with-timeout *uniorg-timeout-duration*))]
(if (zero? (:exit r))
(-> r :out (json/parse-string (comp keyword camel->kebab))))))

View File

@@ -7,7 +7,9 @@
[clojure.tools.logging.readable :as lr]
[com.rpl.specter :as sp]
[net.deertopia.doerg.html :as doerg-html]
[hiccup2.core :as hiccup]
[clojure.pprint]
[net.deertopia.doerg.tex :as tex]
[clojure.zip :as z]))
;;; Top-level API
@@ -43,15 +45,16 @@
(defn org-element-recursive
"Recursively render an Org-mode element to Hiccup."
[e]
(->> e
;; gather-footnotes
(sp/transform
(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)))))))
(render-renderer-error e))))))))
(defn org-document
"Recursively render an Org-mode document to Hiccup."
@@ -276,11 +279,16 @@
(defmethod org-element "citation-reference" [{:keys [key]}]
(str "@" key))
(defmethod org-element "latex-fragment" [{:keys [value]}]
[:span.latex-fragment value])
(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]}]
[:pre [:code value]])
[:span.latex-fragment
(hiccup/raw (tex/render value :display? true))])
(defmethod org-element "example-block" [{:keys [value]}]
[:pre value])

View File

@@ -0,0 +1,49 @@
(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)))