Skip to content

Commit f48b7bd

Browse files
添加figure块
1 parent e721fee commit f48b7bd

File tree

7 files changed

+203
-34
lines changed

7 files changed

+203
-34
lines changed

examples/SupportedGrammer/problem-0.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,19 @@ This is `inline code`
4444

4545
[NOI website](https://noi.cn/)
4646

47+
Inline ![](https://private-static.mrpython.top/cnoi-gen-test-img-small_e06124f5.jpg) image
48+
49+
:::figure{caption=居中图片。在这里添加一些图片描述。}
4750
![1.jpg](https://private-static.mrpython.top/cnoi-gen-test-img_324e0508.jpg)
51+
:::
52+
53+
---
54+
55+
:::figure
56+
caption 参数是可选的。
57+
58+
文本也可以放进去。
59+
:::
4860

4961
<https://luogu.com.cn>
5062

scripts/compile-typst-examples.ts

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,12 @@
11
import fs from "fs/promises";
22
import path from "path";
3-
import remarkTypst from "../src/compiler/remarkTypst";
4-
import { unified } from "unified";
5-
import remarkParse from "remark-parse";
6-
import remarkMath from "remark-math";
7-
import remarkGfm from "remark-gfm";
8-
import type contestData from "../src/types/contestData";
3+
import { type ContestData } from "../src/types/contestData";
94
import { exec } from "child_process";
105
import axiosInstance from "../src/utils/axiosInstance";
6+
import processor from "../src/compiler/processor";
117

128
const TYPST_CMD = process.env.TYPST_CMD || "typst";
139

14-
const processor = unified()
15-
.use(remarkParse)
16-
.use(remarkMath)
17-
.use(remarkGfm)
18-
.use(remarkTypst)
19-
.freeze();
20-
2110
(async () => {
2211
const examplesDir = path.resolve("examples");
2312
const entries = await fs.readdir(examplesDir, { withFileTypes: true });
@@ -29,7 +18,7 @@ const processor = unified()
2918
.readFile(dataJsonPath, { encoding: "utf8" })
3019
.catch(() => undefined);
3120
if (!jsonFile) continue;
32-
const jsonData = JSON.parse(jsonFile) as contestData;
21+
const jsonData = JSON.parse(jsonFile) as ContestData;
3322
for (const mdFileName of [
3423
"precaution.md" as const,
3524
...jsonData.problems.map((_, i) => `problem-${i}.md` as const),

src/compiler/index.ts

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,8 @@
11
import axiosInstance from "@/utils/axiosInstance";
22
import type { PackageSpec } from "@myriaddreamin/typst.ts/internal.types";
33
import { listenMain, send } from "@mr.python/promise-worker-ts";
4-
import remarkGfm from "remark-gfm";
5-
import remarkMath from "remark-math";
6-
import remarkParse from "remark-parse";
7-
import { unified } from "unified";
8-
import remarkTypst from "./remarkTypst";
94
import { isAxiosError } from "axios";
5+
import processor from "./processor";
106
import type { ContestData } from "@/types/contestData";
117
import {
128
type CompileTypstMessage,
@@ -258,13 +254,6 @@ export const typstInitPromise = Promise.all(
258254
throw new Error("Typst initialization failed.", { cause: err });
259255
});
260256

261-
const processor = unified()
262-
.use(remarkParse)
263-
.use(remarkMath)
264-
.use(remarkGfm)
265-
.use(remarkTypst)
266-
.freeze();
267-
268257
function compilerPrepare(
269258
data: ContestData<{ withMarkdown: true }>,
270259
): [ContestData<{ withTypst: true }>, [string, string][]] {

src/compiler/processor.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import remarkDirective from "remark-directive";
2+
import remarkGfm from "remark-gfm";
3+
import remarkMath from "remark-math";
4+
import remarkParse from "remark-parse";
5+
import { unified } from "unified";
6+
import remarkTypst from "./remarkTypst";
7+
8+
const processor = unified()
9+
.use(remarkParse)
10+
.use(remarkMath)
11+
.use(remarkGfm)
12+
.use(remarkDirective)
13+
.use(remarkTypst)
14+
.freeze();
15+
16+
export default processor;

src/compiler/remarkTypst/compiler.ts

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,10 @@ function revert(
8585
data.push(suffix);
8686
}
8787

88+
function fallbackHandler(node: mdast.RootContent, ctx: CompilerContext) {
89+
if ("children" in node)
90+
for (const child of node.children) parseContent(child, ctx);
91+
}
8892
export const handlers = {
8993
text: (node, ctx) => {
9094
const { data } = ctx;
@@ -232,9 +236,9 @@ export const handlers = {
232236
image: (node, ctx) => {
233237
const { data, assets } = ctx;
234238
const assertID = "img-" + hash(node.url);
235-
data.push('#box(figure(image("', assertID);
239+
data.push('#box(image("', assertID);
236240
if (node.alt) data.push('", alt: "', escapeTypstString(node.alt));
237-
data.push('")), width: 100%)');
241+
data.push('"))');
238242
assets.push({
239243
assetUrl: node.url,
240244
filename: assertID,
@@ -260,9 +264,9 @@ export const handlers = {
260264
const def = definitionById.get(node.identifier);
261265
if (def) {
262266
const assertID = "img-" + hash(def.url);
263-
data.push('#box(figure(image("', assertID);
267+
data.push('#box(image("', assertID);
264268
if (node.alt) data.push('", alt: "', escapeTypstString(node.alt));
265-
data.push('")), width: 100%)');
269+
data.push('"))');
266270
assets.push({
267271
assetUrl: def.url,
268272
filename: assertID,
@@ -295,6 +299,27 @@ export const handlers = {
295299
yaml: () => {
296300
// we have no use now, skip it
297301
},
302+
containerDirective: (node, ctx) => {
303+
const { data } = ctx;
304+
if (node.name === "figure") {
305+
data.push("#figure(");
306+
if (node.attributes?.caption)
307+
data.push(
308+
'caption: "',
309+
escapeTypstString(String(node.attributes.caption)),
310+
'", ',
311+
);
312+
data.push(")[\n");
313+
for (const child of node.children) parseContent(child, ctx);
314+
data.push("]\n");
315+
} else fallbackHandler(node, ctx);
316+
},
317+
textDirective: (node, ctx) => {
318+
fallbackHandler(node, ctx);
319+
},
320+
leafDirective: (node, ctx) => {
321+
fallbackHandler(node, ctx);
322+
},
298323
} as const satisfies {
299324
[K in keyof mdast.RootContentMap]: (
300325
node: mdast.RootContentMap[K],

tests/unit/remark-typst.common.test.ts

Lines changed: 139 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -967,7 +967,7 @@ describe("Handlers", () => {
967967
);
968968
const result = ctx.data.join("");
969969
const imageId = result.match(
970-
/^#box\(figure\(image\("(img-[0-9a-f]{16})", alt: "Example\\\\Image"\)\), width: 100%\)$/,
970+
/^#box\(image\("(img-[0-9a-f]{16})", alt: "Example\\\\Image"\)\)$/,
971971
)?.[1];
972972
expect(imageId).toBe("img-" + hash("https://example.com/image.png"));
973973
expect(ctx.assets.length).toBe(1);
@@ -987,7 +987,7 @@ describe("Handlers", () => {
987987
);
988988
const result = ctx.data.join("");
989989
const imageId = result.match(
990-
/^#box\(figure\(image\("(img-[0-9a-f]{16})"\)\), width: 100%\)$/,
990+
/^#box\(image\("(img-[0-9a-f]{16})"\)\)$/,
991991
)?.[1];
992992
expect(imageId).toBe("img-" + hash("https://example.com/image.png"));
993993
expect(ctx.assets.length).toBe(1);
@@ -1082,7 +1082,7 @@ describe("Handlers", () => {
10821082
);
10831083
const result = ctx.data.join("");
10841084
const imageId = result.match(
1085-
/^#box\(figure\(image\("(img-[0-9a-f]{16})", alt: "img \\\\"\)\), width: 100%\)$/,
1085+
/^#box\(image\("(img-[0-9a-f]{16})", alt: "img \\\\"\)\)$/,
10861086
)?.[1];
10871087
expect(imageId).toBe("img-" + hash("https://example.com/ref1.png"));
10881088
expect(ctx.assets.length).toBe(1);
@@ -1217,6 +1217,141 @@ describe("Handlers", () => {
12171217
expect(ctx.definitionById.size).toBe(0);
12181218
expect(ctx.footnoteById.size).toBe(0);
12191219
});
1220+
describe("Figure", () => {
1221+
test("Figure without caption", () => {
1222+
const ctx = initContext();
1223+
handlers.containerDirective(
1224+
{
1225+
type: "containerDirective",
1226+
name: "figure",
1227+
children: [
1228+
{
1229+
type: "paragraph",
1230+
children: [
1231+
{
1232+
type: "text",
1233+
value: "This is a figure caption.",
1234+
},
1235+
],
1236+
},
1237+
{
1238+
type: "paragraph",
1239+
children: [
1240+
{
1241+
type: "text",
1242+
value: "This is figure content.",
1243+
},
1244+
],
1245+
},
1246+
],
1247+
},
1248+
ctx,
1249+
);
1250+
expect(ctx.data.join("")).toBe(`#figure()[
1251+
#par[#"This is a figure caption."]
1252+
#par[#"This is figure content."]
1253+
]
1254+
`);
1255+
});
1256+
test("Figure with caption", () => {
1257+
const ctx = initContext();
1258+
handlers.containerDirective(
1259+
{
1260+
type: "containerDirective",
1261+
name: "figure",
1262+
children: [
1263+
{
1264+
type: "paragraph",
1265+
children: [
1266+
{
1267+
type: "text",
1268+
value: "This is a figure caption.",
1269+
},
1270+
],
1271+
},
1272+
{
1273+
type: "paragraph",
1274+
children: [
1275+
{
1276+
type: "text",
1277+
value: "This is figure content.",
1278+
},
1279+
],
1280+
},
1281+
],
1282+
attributes: {
1283+
caption: "Figure Caption Attribute",
1284+
},
1285+
},
1286+
ctx,
1287+
);
1288+
expect(ctx.data.join(""))
1289+
.toBe(`#figure(caption: "Figure Caption Attribute", )[
1290+
#par[#"This is a figure caption."]
1291+
#par[#"This is figure content."]
1292+
]
1293+
`);
1294+
});
1295+
});
1296+
describe("unknown directive node", () => {
1297+
test("unknown container directive", () => {
1298+
const ctx = initContext();
1299+
handlers.containerDirective(
1300+
{
1301+
type: "containerDirective",
1302+
name: "unknown",
1303+
children: [
1304+
{
1305+
type: "paragraph",
1306+
children: [
1307+
{
1308+
type: "text",
1309+
value: "Some content",
1310+
},
1311+
],
1312+
},
1313+
],
1314+
},
1315+
ctx,
1316+
);
1317+
expect(ctx.data.join("")).toBe(`#par[#"Some content"]
1318+
`);
1319+
});
1320+
test("unknown leaf directive", () => {
1321+
const ctx = initContext();
1322+
handlers.leafDirective(
1323+
{
1324+
type: "leafDirective",
1325+
name: "unknownLeaf",
1326+
children: [
1327+
{
1328+
type: "text",
1329+
value: "Leaf content",
1330+
},
1331+
],
1332+
},
1333+
ctx,
1334+
);
1335+
expect(ctx.data.join("")).toBe(`#"Leaf content"`);
1336+
});
1337+
test("unknown text directive", () => {
1338+
const ctx = initContext();
1339+
handlers.textDirective(
1340+
{
1341+
type: "textDirective",
1342+
name: "unknownText",
1343+
children: [
1344+
{
1345+
type: "text",
1346+
value: "Text content",
1347+
},
1348+
],
1349+
},
1350+
ctx,
1351+
);
1352+
expect(ctx.data.join("")).toBe(`#"Text content"`);
1353+
});
1354+
});
12201355
});
12211356

12221357
test("Compiler Integration Test", () => {
@@ -1366,7 +1501,7 @@ test("Compiler Integration Test", () => {
13661501
13671502
#heading(level: 1, [#"Remark-typst"#"[^fn1]"#" Integration Test"#footnote(label("user-footnote: fn2"))#" Document"])
13681503
#par[#"!["#"Undefined Image Reference"#"][img1]"]
1369-
#par[#box(figure(image("{{hash}}", alt: "Defined Image Reference")), width: 100%)]
1504+
#par[#box(image("{{hash}}", alt: "Defined Image Reference"))]
13701505
#par[#"["#"Undefined Link Reference"#"][link1]"]
13711506
#par[#link("https://example.com/link2", [#"Defined Link Reference"])]
13721507

typst-template/main.typ

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,9 +202,12 @@
202202
}
203203
block(it)
204204
}
205+
205206
#show figure: it => {
206207
pad(top: 9pt, bottom: 6pt, it)
207208
}
209+
#set figure(numbering: none)
210+
208211
#show math.equation: set text(font: "Latin Modern Math")
209212

210213
#set table(stroke: 0.3pt)

0 commit comments

Comments
 (0)