Skip to content

Commit 48dff05

Browse files
committed
Restore evasion of self-contained math
- This will patch any html / reveal template (including user provided to replace math with an appropriate self contained math script). - evasion of self contained will happen by default. this can be disabled by setting `self-contained-math: true`
1 parent 5446fc0 commit 48dff05

File tree

6 files changed

+180
-8
lines changed

6 files changed

+180
-8
lines changed

src/command/render/pandoc.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -439,10 +439,16 @@ export async function runPandoc(
439439
}),
440440
);
441441

442-
const stagedTemplate = await stageTemplate(extras, options.temp, {
443-
template,
444-
partials,
445-
});
442+
// Stage the template and partials
443+
const stagedTemplate = await stageTemplate(
444+
options,
445+
extras,
446+
{
447+
template,
448+
partials,
449+
},
450+
);
451+
446452
allDefaults[kTemplate] = stagedTemplate;
447453
} else {
448454
if (userPartials.length > 0) {

src/command/render/template.ts

Lines changed: 152 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,22 @@
55
*
66
*/
77
import { basename, join } from "path/mod.ts";
8+
import {
9+
kHtmlMathMethod,
10+
kSelfContained,
11+
kSelfContainedMath,
12+
} from "../../config/constants.ts";
813

914
import {
15+
Format,
1016
FormatExtras,
1117
FormatTemplateContext,
1218
Metadata,
1319
} from "../../config/types.ts";
1420
import { copyTo } from "../../core/copy.ts";
15-
import { TempContext } from "../../core/temp.ts";
21+
import { PandocOptions, RenderFlags } from "./types.ts";
22+
import * as ld from "../../core/lodash.ts";
23+
import { isHtmlDocOutput, isRevealjsOutput } from "../../config/format.ts";
1624

1725
export const kPatchedTemplateExt = ".patched";
1826
export const kTemplatePartials = "template-partials";
@@ -25,11 +33,11 @@ export function readPartials(metadata: Metadata) {
2533
}
2634

2735
export async function stageTemplate(
36+
options: PandocOptions,
2837
extras: FormatExtras,
29-
temp: TempContext,
3038
userContext?: FormatTemplateContext,
3139
) {
32-
const stagingDir = temp.createDir();
40+
const stagingDir = options.temp.createDir();
3341
const template = "template.patched";
3442

3543
const stageContext = (
@@ -61,7 +69,14 @@ export async function stageTemplate(
6169
);
6270
const userStaged = await stageContext(stagingDir, template, userContext);
6371
if (formatStaged || userStaged) {
64-
return join(stagingDir, template);
72+
// The path to the newly staged template
73+
const stagedTemplatePath = join(stagingDir, template);
74+
75+
// Apply any patches now that the template is staged
76+
applyTemplatePatches(stagedTemplatePath, options.format, options.flags);
77+
78+
// Return the path to the template
79+
return stagedTemplatePath;
6580
} else {
6681
return undefined;
6782
}
@@ -81,3 +96,136 @@ export function cleanTemplatePartialMetadata(
8196
}
8297
}
8398
}
99+
100+
interface TemplatePatch {
101+
searchValue: RegExp;
102+
contents: string;
103+
}
104+
105+
function applyTemplatePatches(
106+
template: string,
107+
format: Format,
108+
flags?: RenderFlags,
109+
) {
110+
// The patches to apply
111+
const patches: TemplatePatch[] = [];
112+
113+
// make math evade self-contained for HTML and Reveal
114+
if (isHtmlDocOutput(format.pandoc) || isRevealjsOutput(format.pandoc)) {
115+
if (
116+
((flags && flags[kSelfContained]) || format.pandoc[kSelfContained]) &&
117+
!format.render[kSelfContainedMath]
118+
) {
119+
const math = mathConfig(format, flags);
120+
if (math) {
121+
const mathTemplate = math.method === "mathjax"
122+
? mathjaxScript(math.url)
123+
: math.method == "katex"
124+
? katexScript(math.url)
125+
: "";
126+
127+
if (mathTemplate) {
128+
patches.push({
129+
searchValue: /\$math\$/,
130+
contents: mathTemplate,
131+
});
132+
}
133+
}
134+
}
135+
}
136+
137+
// Apply any patches
138+
if (patches.length) {
139+
let templateContents = Deno.readTextFileSync(template);
140+
patches.forEach((patch) => {
141+
templateContents = templateContents.replace(
142+
patch.searchValue,
143+
patch.contents,
144+
);
145+
});
146+
Deno.writeTextFileSync(template, templateContents);
147+
}
148+
}
149+
150+
function mathConfig(format: Format, flags?: RenderFlags) {
151+
// if any command line math flags were passed then bail
152+
if (
153+
flags?.mathjax || flags?.katex || flags?.webtex || flags?.gladtex ||
154+
flags?.mathml
155+
) {
156+
return undefined;
157+
}
158+
159+
const math = format.pandoc[kHtmlMathMethod];
160+
if (math === undefined || math === "mathjax") {
161+
return {
162+
method: "mathjax",
163+
url: "https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml-full.js",
164+
};
165+
} else if (math === "katex") {
166+
return {
167+
method: "katex",
168+
url: "https://cdn.jsdelivr.net/npm/[email protected]/dist/",
169+
};
170+
} else if (ld.isObject(math)) {
171+
const mathMethod = math as { method: string; url: string };
172+
if (
173+
(mathMethod.method === "mathjax" || mathMethod.method === "katex") &&
174+
typeof (mathMethod.url) === "string"
175+
) {
176+
return mathMethod;
177+
}
178+
}
179+
}
180+
181+
function mathjaxScript(url: string) {
182+
return `
183+
<script>
184+
(function () {
185+
var script = document.createElement("script");
186+
script.type = "text/javascript";
187+
script.src = "${url}";
188+
document.getElementsByTagName("head")[0].appendChild(script);
189+
})();
190+
</script>
191+
`;
192+
}
193+
194+
function katexScript(url: string) {
195+
url = url.trim();
196+
if (!url.endsWith("/")) {
197+
url += "/";
198+
}
199+
return `
200+
<script>
201+
document.addEventListener("DOMContentLoaded", function () {
202+
var head = document.getElementsByTagName("head")[0];
203+
var link = document.createElement("link");
204+
link.rel = "stylesheet";
205+
link.href = "${url}katex.min.css";
206+
head.appendChild(link);
207+
208+
var script = document.createElement("script");
209+
script.type = "text/javascript";
210+
script.src = "${url}katex.min.js";
211+
script.async = false;
212+
script.addEventListener('load', function() {
213+
var mathElements = document.getElementsByClassName("math");
214+
var macros = [];
215+
for (var i = 0; i < mathElements.length; i++) {
216+
var texText = mathElements[i].firstChild;
217+
if (mathElements[i].tagName == "SPAN") {
218+
window.katex.render(texText.data, mathElements[i], {
219+
displayMode: mathElements[i].classList.contains('display'),
220+
throwOnError: false,
221+
macros: macros,
222+
fleqn: false
223+
});
224+
}
225+
}
226+
});
227+
head.appendChild(script);
228+
});
229+
</script>
230+
`;
231+
}

src/config/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export const kTblCap = "tbl-cap";
6464
export const kTblColwidths = "tbl-colwidths";
6565
export const kMergeIncludes = "merge-includes";
6666
export const kPreferHtml = "prefer-html";
67+
export const kSelfContainedMath = "self-contained-math";
6768

6869
export const kLatexAutoMk = "latex-auto-mk";
6970
export const kLatexAutoInstall = "latex-auto-install";
@@ -128,6 +129,7 @@ export const kRenderDefaultsKeys = [
128129
kCodeTools,
129130
kShortcodes,
130131
kTblColwidths,
132+
kSelfContainedMath,
131133
kLatexAutoMk,
132134
kLatexAutoInstall,
133135
kLatexMinRuns,

src/config/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ import {
150150
kSectionTitleReferences,
151151
kSectionTitleReuse,
152152
kSelfContained,
153+
kSelfContainedMath,
153154
kShiftHeadingLevelBy,
154155
kShortcodes,
155156
kSlideLevel,
@@ -330,6 +331,7 @@ export interface FormatRender {
330331
[kLinkExternalIcon]?: string | boolean;
331332
[kLinkExternalNewwindow]?: boolean;
332333
[kLinkExternalFilter]?: string;
334+
[kSelfContainedMath]?: boolean;
333335
}
334336

335337
export interface FormatExecute {

src/format/formats-shared.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ import {
6060
kPageWidth,
6161
kPreferHtml,
6262
kQuartoVersion,
63+
kSelfContainedMath,
6364
kStandalone,
6465
kTblColwidths,
6566
kWarning,
@@ -219,6 +220,7 @@ function defaultFormat(): Format {
219220
[kLatexOutputDir]: null,
220221
[kLinkExternalIcon]: false,
221222
[kLinkExternalNewwindow]: false,
223+
[kSelfContainedMath]: false,
222224
},
223225
pandoc: {},
224226
language: {},

src/resources/schema/document-render.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,18 @@
5959
`data-external="1"` will be left alone; the documents they
6060
link to will not be incorporated in the document.
6161
62+
- name: self-contained-math
63+
tags:
64+
formats: [$html-files]
65+
schema: boolean
66+
default: false
67+
description:
68+
short: "Embed math libraries (e.g. MathJax) within `self-contained` output."
69+
long: |
70+
Embed math libraries (e.g. MathJax) within `self-contained` output.
71+
Note that math libraries are not embedded by default because they are
72+
quite large and often time consuming to download.
73+
6274
- name: filters
6375
schema:
6476
ref: pandoc-format-filters

0 commit comments

Comments
 (0)