Compare commits

...

10 Commits

Author SHA1 Message Date
71163b9bb5 AAAAAA xelatex 2026-02-26 13:07:07 -07:00
7d12f7d90e 2026-02-26 13:04:31 -07:00
d5ccadb326 2026-02-26 12:07:07 -07:00
ee86f0d643 2026-02-26 11:15:05 -07:00
e67f62e5b0 2026-02-26 11:07:12 -07:00
b765f71b55 2026-02-26 10:40:06 -07:00
47b201fd03 2026-02-26 10:31:17 -07:00
8ec78c040a 2026-02-26 09:04:49 -07:00
d15f9988c2 2026-02-26 00:32:11 -07:00
b3ca176ee2 2026-02-26 00:12:28 -07:00
8 changed files with 155 additions and 52 deletions

6
doerg/doerg-tex/deps.edn Normal file
View 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"]}

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

View File

@@ -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")

View File

@@ -2,7 +2,8 @@
\usepackage{amsmath}
\usepackage[active,tightpage,auctex,dvips]{preview}
\usepackage{fontspec}
%% \usepackage{syd-plex}
\usepackage{ifxetex}
\usepackage{syd-plex}
\begin{document}
\setlength\abovedisplayskip{0pt}

View File

@@ -542,3 +542,7 @@ figure.fullwidth figcaption {
; max-width: 55%
; font-size: 1.5rem
}
.latex-fragment
{ fill: currentColor
}

View File

@@ -10,7 +10,8 @@
[hiccup2.core :as hiccup]
[clojure.pprint]
[net.deertopia.doerg.tex :as tex]
[clojure.zip :as z]))
[clojure.zip :as z]
[babashka.fs :as fs]))
;;; Top-level API
@@ -40,33 +41,33 @@
(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/binding-temml-worker
(let [rendered (-> doc gather-footnotes render-tex-snippets
org-element-recursive)]
[:html
[:head
[:title "org document"]
doerg-html/head]
[:body
[:article
rendered]]])))
;;; Further dispatching on `org-element`
@@ -126,6 +127,79 @@
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'?>" "")
(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 [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/render-xelatex svg-dir)))]
(def the-rendered-snippets rendered-snippets)
(doseq [{:keys [promise node]} @promises]
(try (let [{:keys [value contents]} node
temml (tex/render-temml (or contents value))]
(if (tex/erroneous-temml-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
(prn e)
(flush)
(throw e))))
(when (fs/exists? "/tmp/doerg-svgs")
(fs/delete-tree "/tmp/doerg-svgs"))
(fs/copy-tree svg-dir "/tmp/doerg-svgs"))))]
(future-call (bound-fn* f))
r))
(comment
(render-tex-snippets doc))
(defn- render-pprint
@@ -269,15 +343,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 #_#_ 2000 "«timed out»"))])
(defmethod org-element "latex-environment" [{:keys [value] :as e}]
[:span.latex-fragment
(-> e ::rendered (deref #_#_ 2000 "«timed out»"))])
(defmethod org-element "example-block" [{:keys [value]}]
[:pre value])
@@ -296,7 +367,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.
@@ -319,4 +389,3 @@
[:span.org-link.external
[:a {:href raw-link}
(or (seq children) raw-link)]])

View File

@@ -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"
"/tmp/t.org")
(defn- force-create-sym-link [path target]
(fs/delete-if-exists path)

View File

@@ -116,7 +116,13 @@
slurp
(str/replace-first "% {{contents}}" contents))))
(defn render-xelatex [output-dir & snippets]
(defn render-xelatex
"Render a collection of `snippets` to SVGs in `output-dir` using
XeLaTeX and dvisvgm. Returns a map whose keys are `snippets` and
whose values are maps containing dimensional info, a string of
errors output by XeLaTeX, 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-xelatex"}]
(let [preview-tex (fs/file dir "preview.tex")
preview-xdv (fs/file dir "preview.xdv")
@@ -135,7 +141,8 @@
{snippet
(-> dimensions
(assoc
:file (fs/file dir (format "%09d.svg" (inc ix)))))})
:file (fs/file output-dir
(format "%09d.svg" (inc ix)))))})
(range)
distinct-snippets
dimensions)
@@ -154,46 +161,50 @@
;;; Temml
(def ^:dynamic *tex-worker-timeout-duration*
(def ^:dynamic *temml-worker-timeout-duration*
"Number of milliseconds to wait before killing the external Uniorg
process."
(* 10 1000))
(def ^:dynamic *worker*)
(def ^:dynamic *temml-worker*)
(defn tex-worker [& {:keys [preamble]}]
(defn temml-worker [& {:keys [preamble]}]
(p/process
{:shutdown p/destroy-tree
:err :inherit}
:err (l/log-stream :info "temml/err")}
#_"doerg-tex"
"./doerg-tex/index.js"
"--preamble"
"resources/net/deertopia/doerg/prelude.tex"))
(defn finish [tw]
(defn close-temml-worker [tw]
(.close (:in tw)))
(defmacro with-tex-worker [tw & body]
`(let [~tw (tex-worker)]
(defmacro with-temml-worker [tw & body]
`(let [~tw (temml-worker)]
(try
(do ~@body)
(finally
(finish ~tw)
(close-temml-worker ~tw)
(p/destroy-tree ~tw)))))
(defmacro binding-tex-worker [& body]
`(binding [*worker* (tex-worker)]
(defmacro binding-temml-worker [& body]
`(binding [*temml-worker* (temml-worker)]
(try
~@body
(finally
(finish *worker*)))))
(close-temml-worker *temml-worker*)))))
(defn command [x]
(cbor/encode cbor/default-codec (:in *worker*) x)
(.flush (:in *worker*))
(cbor/decode cbor/default-codec (:out *worker*)))
(defn command-temml-worker [x]
(cbor/encode cbor/default-codec (:in *temml-worker*) x)
(.flush (:in *temml-worker*))
(cbor/decode cbor/default-codec (:out *temml-worker*)))
(defn render [s & {:keys [display?]}]
(defn render-temml [s & {:keys [display?]}]
(if display?
(command [s])
(command s)))
(command-temml-worker [s])
(command-temml-worker s)))
;; hackky....
(defn erroneous-temml-output? [s]
(re-find #"(#b22222|temml-error)" s))