Compare commits

..

38 Commits

Author SHA1 Message Date
a3ebbf13cd refactor: rename doerg-tex → doerg-temml-worker 2026-02-28 21:39:59 -07:00
7aa6e02b9e fix: nix stuff and config 2026-02-28 21:26:05 -07:00
01d88bb30d fix: config spec 2026-02-28 20:21:23 -07:00
7e0826c496 feat: kaocha 2026-02-28 20:20:13 -07:00
06c20588f2 fix: align display math 2026-02-28 16:50:00 -07:00
4b4573ee1d feat: tests 2026-02-28 16:31:27 -07:00
071a15ca51 2026-02-27 19:41:40 -07:00
f811a519b5 feat: to-html 2026-02-27 18:48:15 -07:00
fa42052a6a refactor: split tex modules 2026-02-27 18:21:10 -07:00
ea3b77619a fix: render-temml 2026-02-26 14:43:11 -07:00
cf225f6bc5 fix: temml css 2026-02-26 14:33:44 -07:00
2776144f04 refactor: split up render-temml 2026-02-26 13:40:50 -07:00
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
efd4c4cdbc 2026-02-25 17:07:48 -07:00
f6da1187ad 2026-02-25 17:06:33 -07:00
bd3e6016ed 2026-02-25 16:45:58 -07:00
02bbe0a8b8 2026-02-25 09:55:32 -07:00
dc31f02b30 2026-02-25 09:48:33 -07:00
fd8354f398 2026-02-25 09:24:51 -07:00
af493a1291 2026-02-25 09:16:54 -07:00
955db64027 2026-02-24 17:52:49 -07:00
ad8ae8f743 2026-02-24 17:50:42 -07:00
a52bc97ed3 2026-02-24 15:05:22 -07:00
470dec9183 2026-02-24 10:50:03 -07:00
790ce7e01f 2026-02-24 10:43:43 -07:00
aa8b89fe84 2026-02-24 09:43:47 -07:00
638b12c9eb 2026-02-24 09:11:28 -07:00
9ac8577478 2026-02-23 16:25:17 -07:00
b5a5721eed wip: xelatex 2026-02-22 19:01:16 -07:00
21 changed files with 116 additions and 230 deletions

View File

@@ -1,11 +0,0 @@
name: build
on: [push]
jobs:
build:
runs-on: nixos
steps:
- name: Check out repository code
uses: actions/checkout@v4
- name: build doerg
run: nix build -L .#doerg

0
doerg/doerg-parser/index.js Executable file → Normal file
View File

View File

@@ -1,17 +1,11 @@
{
"name": "doerg-parser",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "doerg-parser",
"version": "0.1.0",
"dependencies": {
"uniorg-parse": "^3.2.0"
},
"bin": {
"doerg-parser": "index.js"
}
},
"node_modules/@types/unist": {

View File

@@ -1,12 +1 @@
#+title: doerg-temml-worker
This is a tiny Node script that is used by doerg. For each CBOR-encoded value on stdin (a "command"), a response value is spit to stdout.
Currently, the following commands are supported:
- Any string: Return the Temml-rendered MathML code as a string (inline).
- An array with a single string element: Return the Temml-rendered MathML code as a string (display).
If something goes wrong, either of these commands may return an object of form
#+begin_src json
{"type": "error"
,"error": «error object»}
#+end_src

View File

@@ -8,13 +8,7 @@
buildNpmPackage {
pname = "doerg-temml-worker";
version = "0.1.0";
src =
builtins.filterSource
(name: _file-type: ! builtins.elem name [
"deserialise.clj" "deps.edn" "default.nix"
"README.org" "serialise.clj" "node_modules"
])
./.;
src = ./.;
npmDeps = importNpmLock { npmRoot = ./.; };
npmConfigHook = importNpmLock.npmConfigHook;
dontNpmBuild = true;

View File

@@ -3,4 +3,4 @@
com.rpl/specter {:mvn/version "1.1.6"}
mvxcvi/clj-cbor {:mvn/version "1.1.1"}
babashka/process {:mvn/version "0.6.25"}}
:paths ["."]}
:paths ["." "classes"]}

View File

@@ -1,6 +1,4 @@
(ns deserialise
"A toy script for conveniently interfacing with doerg-temml-worker
during development."
(:require [clj-cbor.core :as cbor]
[clojure.string :as str]))

View File

@@ -1,11 +1,11 @@
{
"name": "doerg-temml-worker",
"name": "doerg-tex",
"version": "0.1.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "doerg-temml-worker",
"name": "doerg-tex",
"version": "0.1.0",
"dependencies": {
"cbor-x": "^1.6.0",
@@ -13,7 +13,7 @@
"temml": "^0.13.1"
},
"bin": {
"doerg-temml-worker": "index.js"
"doerg-tex": "index.js"
}
},
"node_modules/@cbor-extract/cbor-extract-darwin-arm64": {

View File

@@ -1,6 +1,4 @@
(ns serialise
"A toy script for conveniently interfacing with doerg-temml-worker
during development."
(:require [clj-cbor.core :as cbor]
[clojure.string :as str]))
@@ -10,4 +8,12 @@
(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")
(w "\\alpha^\\beta")

View File

@@ -39,6 +39,5 @@ texlive.combine {
collection-fontsrecommended # Essential fonts.
etoolbox # For Org-mode exports.
caption
standalone
;
}

View File

@@ -7,6 +7,7 @@
, fake-git
, our-tex ? callPackage ./our-tex.nix {}
, makeWrapper
, breakpointHook
}:
let
@@ -15,9 +16,9 @@ let
mkCljBin' = args: (mkCljBin args).overrideAttrs (final: prev: {
nativeBuildInputs =
builtins.filter
# A possibly-sketchy predicate, lol.
(x: x != fake-git)
prev.nativeBuildInputs;
# A possibly-sketchy predicate, lol.
(x: x != fake-git)
prev.nativeBuildInputs;
});
plex = ibm-plex-web.override {
@@ -31,12 +32,13 @@ let
in mkCljBin' {
name = "net.deertopia/doerg";
version = "0.1.0";
projectSrc = lib.cleanSource ./.;
projectSrc = ./.;
lockfile = ../deps-lock.json;
main-ns = "net.deertopia.doerg.main";
nativeBuildInputs = [
plex
makeWrapper
breakpointHook
];
buildInputs = [
doerg-parser
@@ -58,4 +60,9 @@ in mkCljBin' {
wrapProgram $out/bin/doerg \
--prefix PATH : ${bin-path}
'';
# installPhase= ''
# runHook preInstall
# exit 1
# runHook postInstall
# '';
}

View File

@@ -1,56 +1,11 @@
\documentclass[tikz,dvisvgm]{standalone}
\usepackage[active,tightpage,auctex,dvips]{preview}
\documentclass{article}
\usepackage{amsmath}
\usepackage{amssymb}
\usepackage[active,tightpage,auctex,dvips]{preview}
\usepackage{fontspec}
\usepackage{ifxetex}
\usepackage{syd-plex}
\usepackage{quiver}
\usepackage{tikz}
\usepackage{amscd}
\newcommand{\catname}[1]{\textbf{#1}}
\newcommand{\C}[1]{\catname{#1}}
% Specific categories
\newcommand{\Set}{\catname{Set}}
\newcommand{\Cat}{\catname{Cat}}
\newcommand{\CAT}{\catname{CAT}}
\newcommand{\Grp}{\catname{Grp}}
% Common objects and functions
\newcommand{\Ob}{\operatorname{Ob}}
\newcommand{\op}{\text{op}}
\newcommand{\opof}[1]{{#1}^{\text{op}}}
% \id{x} is the identity morphism on x.
\newcommand{\id}[1]{1_{#1}}
% \Id{C} is the identity functor on C.
\newcommand{\Id}[1]{1_{#1}}
\newcommand{\Mor}{\operatorname{Mor}}
\newcommand{\homset}[3]{{{#1} \left[ {#2} \to {#3} \right]}}
% Constructions of categories.
\newcommand{\FunctorCategory}[2]{\left[#1, #2\right]}
\newcommand{\Funct}[2]{\FunctorCategory{#1}{#2}} % Deprecated.
\newcommand{\ArrowCategory}[1]{{#1}^\to}
% Semantic aliases
\newcommand{\monicto}{\rightarrowtail}
\newcommand{\epicto}{\twoheadrightarrow}
\newcommand{\naturalto}{\Rightarrow}
\newcommand{\isoto}{\cong}
\newcommand{\equivto}{\simeq}
\newcommand{\naturaltrans}[2]{#1 \naturalto #2} % Deprecated.
\newcommand{\horizontalcompose}{\ast}
% Optics
\newcommand{\opticname}[1]{\textbf{#1}}
\newcommand{\optic}[3]{\opticname{#1}^\prime\;#2\;#3}
\newcommand{\Optic}[5]{\opticname{#1}\;#2\;#3\;#4\;#5}
\begin{document}
\setlength\abovedisplayskip{0pt} % Remove padding before equation environments.
%% \color[rgb]{0.000,0.000,0.004}\special{dvisvgm:currentcolor on}\setcounter{equation}{0}%
\setlength\abovedisplayskip{0pt}
% {{contents}}
\end{document}

View File

@@ -6,38 +6,30 @@
(s/def ::config
(s/keys :req [::ibm-plex-web
::latex
::dvisvgm
::doerg-temml-worker
::doerg-parser]))
::dvisvgm]))
(s/def ::file
#(or (instance? java.io.File %)
(string? %)))
(s/def ::executable #(or (fs/executable? %)
(and (fs/relative? %)
(fs/which %))))
(def default
{::ibm-plex-web
(fs/file (some #(let [x (fs/path % "ibm-plex-web")]
(and (fs/exists? x) x))
(fs/split-paths (System/getenv "XDG_DATA_DIRS"))))
(or (System/getenv "DOERG_IBM_PLEX_WEB")
(fs/file (some #(let [x (fs/path % "ibm-plex-web")]
(and (fs/exists? x) x))
(fs/split-paths (System/getenv "XDG_DATA_DIRS")))))
::latex "xelatex"
::dvisvgm "dvisvgm"
;; TODO: Can we automatically set this to "./doerg-temml-worker/index.js" in
;; a development environment?
::doerg-temml-worker "doerg-temml-worker"
::doerg-parser "doerg-parser"})
::doerg-temml-worker "doerg-temml-worker"})
(def ^:dynamic *cfg* default)
(s/def ::ibm-plex-web ::file)
(s/def ::latex ::executable)
(s/def ::latex ::file)
(s/def ::dvisvgm ::executable)
(s/def ::dvisvgm ::file)
(s/def ::doerg-temml-worker ::executable)
(s/def ::doerg-parser ::executable)
(s/def ::doerg-temml-worker ::file)

View File

@@ -15,6 +15,8 @@
(:refer-clojure :exclude [read-string]))
(defonce ^:private uniorg-script-path-atom (atom nil))
(def ^:dynamic *uniorg-timeout-duration*
"Number of milliseconds to wait before killing the external Uniorg
process."
@@ -29,7 +31,7 @@
:or {in *in*}}]
(let [r (-> (p/process
{:in in :out :string}
(::cfg/doerg-parser cfg/*cfg*))
"doerg-parser")
(common/deref-with-timeout *uniorg-timeout-duration*))]
(if (zero? (:exit r))
(-> r :out (json/parse-string (comp keyword camel->kebab))))))

View File

@@ -9,9 +9,9 @@
[net.deertopia.doerg.html :as doerg-html]
[hiccup2.core :as hiccup]
[clojure.pprint]
;; #_
#_
[net.deertopia.doerg.tex :as tex]
;; [net.deertopia.doerg.tex.native :as tex-native]
[net.deertopia.doerg.tex.native :as tex-native]
[net.deertopia.doerg.tex.temml :as tex-temml]
[clojure.zip :as z]
[babashka.fs :as fs]))
@@ -143,38 +143,78 @@
#(element/of-keyword-type? % "LATEX_HEADER")
(sp/view :value)])))
(defn- timeout-snippet-promises [snippet-promises fut]
;; Time out after twenty seconds. With all the LaTeX and IPC, there
;; are so many opportunities for things to go wrong </3.
(let [ms (* 20 1000)
fut-res (deref fut ms ::timed-out)]
(if (= fut-res ::timed-out)
(do (l/warnf "Giving up on rendering TeX snippets after %.3f seconds."
(/ ms 1000))
(future-cancel fut)
(doseq [[_snippet p] snippet-promises]
(deliver p ::timed-out)))
fut-res)))
(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 [snippet-promises (atom [])
(let [promises (atom [])
r (->> doc (sp/transform
[element/postorder-walker
#(element/of-type?
% "latex-fragment" "latex-environment")]
(fn [node]
(let [p (promise)]
(swap! snippet-promises #(conj % [(:value node) p]))
(swap! promises #(conj % {:promise p :node node}))
(assoc node ::rendered p)))))
sp @snippet-promises
fut (-> #(tex/render-snippets sp)
bound-fn* future-call)]
(timeout-snippet-promises sp fut)
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)))))))
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

View File

@@ -14,26 +14,16 @@
"/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/20251021155921-path_action.org"
#_
"test/net/deertopia/doerg/render_test/fallbacks.org"
#_
"/home/msyds/org/20250910115311-men_who_would_make_stunning_dykes.org")
"test/net/deertopia/doerg/render_test/fallbacks.org")
(defn- force-create-sym-link [path target]
(fs/delete-if-exists path)
(fs/create-sym-link path target))
(defn reconfigure-doerg! []
(alter-var-root
#'cfg/*cfg*
#(assoc %
::cfg/doerg-temml-worker "./doerg-temml-worker/index.js"
::cfg/doerg-parser "./doerg-parser/index.js")))
(defn render-html [& {:keys [src dest]
:or {src some-org-file
dest "/tmp/doerg-test"}}]

View File

@@ -1,71 +1,4 @@
(ns net.deertopia.doerg.tex
(:require [net.deertopia.doerg.tex.native :as native]
[net.deertopia.doerg.tex.temml :as temml]
[babashka.fs :as fs]
[clojure.string :as str]
[hiccup2.core :as hiccup]
[clojure.tools.logging :as l]
[clojure.tools.logging.readable :as lr]))
(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)))))
;; Stupid hack. --currentcolor on dvisvgm should be enough, but
;; it doesn't get e.g. TikZ arrows.
(str/replace #"stroke=['\"]#000['\"]" "stroke=\"currentColor\"")))
(def ^:dynamic *save-snippets?* false)
(defn render-snippets [snippet-promises]
(with-redefs [fs/delete-tree
(fn
([path]
(l/warnf "refusing to delete %s" path))
([path opts]
(lr/warnf "refusing to delete %s with opts %s"
path opts)))]
(fs/with-temp-dir [svg-dir {:prefix "doerg-svg-"}]
(let [rendered-snippets
(delay (->> snippet-promises
(map first)
(apply native/render svg-dir)))]
(doseq [[snippet p] snippet-promises]
(try (let [temml (temml/render snippet)]
(->> (if (temml/erroneous-output? temml)
(let [tex (get @rendered-snippets snippet)]
(if (:errors tex)
temml
(read-and-patch-generated-svg tex)))
temml)
hiccup/raw (deliver p)))
(catch Exception e
(l/error e "Error in TeX thread")
(throw e))))))))
(comment
(let [snippets (for [x ["\\(\\ifxetex blah \\fi\\)"
"\\(\\sqrt{x^2 + y^2}\\)"]]
[x (promise)])]
(temml/binding-worker
(render-snippets snippets)
(map #(-> % second deref) snippets))))
[babashka.fs :as fs]))

View File

@@ -93,7 +93,7 @@
(invoke
{:dir output-dir}
dvisvgm "--page=1-" "--optimize" "--clipjoin"
"--relative" "--no-fonts" "-v3" "--currentcolor"
"--relative" "--no-fonts" "-v3"
"--message=processing page {?pageno}: output written to {?svgpath}"
"--bbox=preview" "-o" "%9p.svg" file)))

View File

@@ -55,18 +55,13 @@
(command-worker [s]))
(defn render [s]
(let [s (str/trim 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 (IllegalArgumentException.
(ex-info
(str "`net.deertopia.doerg.tex.temml` argument should"
" be enclosed in math delimiters.")
{:arg 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]

View File

@@ -41,8 +41,8 @@
vendored = final.callPackage ./vendor {};
in {
inherit (vendored) ibm-plex-web;
publisher = final.callPackage ./publisher {};
doerg = final.callPackage ./doerg {};
publisher = final.callPackage ./publisher/package.nix {};
doerg = final.callPackage ./doerg/package.nix {};
doerg-parser = final.callPackage ./doerg/doerg-parser {};
doerg-temml-worker = final.callPackage ./doerg/doerg-temml-worker {};
};
@@ -65,6 +65,9 @@
clojure-lsp
doerg-parser
doerg-temml-worker
# wahhh ibm-plex-web is a dependency of doerg... why must
# i specify it hereeee.
# ibm-plex-web
zprint
clojure
babashka