-
Notifications
You must be signed in to change notification settings - Fork 18
/
environment-subs.ts
172 lines (161 loc) · 5.8 KB
/
environment-subs.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
import cssesc from "cssesc";
import {
parseTabularSpec,
TabularColumn,
} from "@unified-latex/unified-latex-ctan/package/tabularx";
import { htmlLike } from "@unified-latex/unified-latex-util-html-like";
import * as Ast from "@unified-latex/unified-latex-types";
import { parseAlignEnvironment } from "@unified-latex/unified-latex-util-align";
import {
getArgsContent,
getNamedArgsContent,
} from "@unified-latex/unified-latex-util-arguments";
import { match } from "@unified-latex/unified-latex-util-match";
import { printRaw } from "@unified-latex/unified-latex-util-print-raw";
import { wrapPars } from "../wrap-pars";
const ITEM_ARG_NAMES_REG = ["label"] as const;
const ITEM_ARG_NAMES_BEAMER = [null, "label", null] as const;
type ItemArgs = Record<typeof ITEM_ARG_NAMES_REG[number], Ast.Node[] | null> & {
body: Ast.Node[];
};
/**
* Extract the arguments to an `\item` macro.
*/
function getItemArgs(node: Ast.Macro): ItemArgs {
if (!Array.isArray(node.args)) {
throw new Error(
`Cannot find \\item macros arguments; you must attach the \\item body to the macro before calling this function ${JSON.stringify(
node
)}`
);
}
// The "body" has been added as a last argument to the `\item` node. We
// ignore this argument when comparing argument signatures.
const argNames =
node.args.length - 1 === ITEM_ARG_NAMES_BEAMER.length
? ITEM_ARG_NAMES_BEAMER
: ITEM_ARG_NAMES_REG;
const ret = Object.assign(
{ body: node.args[node.args.length - 1].content },
getNamedArgsContent(node, argNames)
);
return ret as ItemArgs;
}
function enumerateFactory(parentTag = "ol", className = "enumerate") {
return function enumerateToHtml(env: Ast.Environment) {
// The body of an enumerate has already been processed and all relevant parts have
// been attached to \item macros as arguments.
const items = env.content.filter((node) => match.macro(node, "item"));
const content = items.flatMap((node) => {
if (!match.macro(node) || !node.args) {
return [];
}
const attributes: Record<string, string | Record<string, string>> =
{};
// Figure out if there any manually-specified item labels. If there are,
// we need to specify a custom list-style-type.
// We test the open mark to see if an optional argument was actually supplied.
const namedArgs = getItemArgs(node);
if (namedArgs.label != null) {
const formattedLabel = cssesc(printRaw(namedArgs.label || []));
attributes.style = {
// Note the space after `formattedLabel`. That is on purpose!
"list-style-type": formattedLabel
? `'${formattedLabel} '`
: "none",
};
}
const body = namedArgs.body;
return htmlLike({
tag: "li",
content: wrapPars(body),
attributes,
});
});
return htmlLike({
tag: parentTag,
attributes: { className },
content,
});
};
}
function createCenteredElement(env: Ast.Environment) {
return htmlLike({
tag: "center",
attributes: { className: "center" },
content: env.content,
});
}
function createTableFromTabular(env: Ast.Environment) {
const tabularBody = parseAlignEnvironment(env.content);
const args = getArgsContent(env);
let columnSpecs: TabularColumn[] = [];
try {
columnSpecs = parseTabularSpec(args[1] || []);
} catch (e) {}
const tableBody = tabularBody.map((row) => {
const content = row.cells.map((cell, i) => {
const columnSpec = columnSpecs[i];
const styles: Record<string, string> = {};
if (columnSpec) {
const { alignment } = columnSpec;
if (alignment.alignment === "center") {
styles["text-align"] = "center";
}
if (alignment.alignment === "right") {
styles["text-align"] = "right";
}
if (
columnSpec.pre_dividers.some(
(div) => div.type === "vert_divider"
)
) {
styles["border-left"] = "1px solid";
}
if (
columnSpec.post_dividers.some(
(div) => div.type === "vert_divider"
)
) {
styles["border-right"] = "1px solid";
}
}
return htmlLike(
Object.keys(styles).length > 0
? {
tag: "td",
content: cell,
attributes: { style: styles },
}
: {
tag: "td",
content: cell,
}
);
});
return htmlLike({ tag: "tr", content });
});
return htmlLike({
tag: "table",
content: [
htmlLike({
tag: "tbody",
content: tableBody,
}),
],
attributes: { className: "tabular" },
});
}
/**
* Rules for replacing a macro with an html-like macro
* that will render has html when printed.
*/
export const environmentReplacements: Record<
string,
(node: Ast.Environment) => Ast.Macro | Ast.String | Ast.Environment
> = {
enumerate: enumerateFactory("ol"),
itemize: enumerateFactory("ul", "itemize"),
center: createCenteredElement,
tabular: createTableFromTabular,
};