在 SolidJS 中展示 Typst

2024-09-07

先上效果:

Portal

Typst.ts with SolidJS Demo

主要使用了 typst.ts 项目,因为有React的example所以适配起Solidjs的起来也十分方便。

首先要用 typst-ts-cli 将typ文档编译成vector格式(后缀 artifact.sir.in)然后再加载。

安装cli:

Terminal window
cargo install --locked --git https://github.com/Myriad-Dreamin/typst.ts typst-ts-cli

编译一下,用法和typst的cli基本相同:

typst Project Dir
typst-ts-cli compile --entry ./example.typ --format vector

然后把产生的 example.artifact.sir.in 当静态资源放着,比如public文件夹里。

Tips
截至24年9月,typst-ts-cli 不同版本似乎编译产物有很大差异,去年12月的release编译的vector数据不能被从主分支编译的wasm正常解析,建议版本对上号。

开始编写组件

因为有了react的前车之鉴所以这个非常简单。

然后下文的 local-font permission query 只在 chromium 用得上,firefox似乎还没实现。

TypstDocument.ts
import { withGlobalRenderer } from "@myriaddreamin/typst.ts/dist/esm/contrib/global-renderer.mjs";
import * as typst from "@myriaddreamin/typst.ts";
import { createEffect, createSignal } from "solid-js";
export interface TypstDocumentProps {
fill?: string;
artifact?: Uint8Array;
format?: "vector";
}
let moduleInitOptions: typst.InitOptions = {
beforeBuild: [],
getModule: () =>
"_build/node_modules/@myriaddreamin/typst-ts-renderer/pkg/typst_ts_renderer_bg.wasm",
};
export const TypstDocument = ({
fill,
artifact,
format,
}: TypstDocumentProps) => {
/// --- beg: manipulate permission --- ///
// todo: acquire permission.
const [permission, setPermissionInternal] = createSignal(false);
const setPermissionAndOk = (status: PermissionStatus) => {
if (status.state === "granted") {
setPermissionInternal(true);
return true;
}
setPermissionInternal(false);
return false;
};
createEffect(() => {
/// only works in chromium
navigator.permissions.query({ name: 'local-fonts' as PermissionName }).then(status => {
if (setPermissionAndOk(status)) {
return false;
}
status.addEventListener('change', event => {
console.log(event, status);
setPermissionAndOk(status);
});
});
});
/// --- end: manipulate permission --- ///
/// --- beg: update document --- ///
const [displayDivRef, setDisplayDivRef] = createSignal<
HTMLDivElement | undefined
>();
createEffect(() => {
const doRender = (renderer: typst.TypstRenderer) => {
const divElem = displayDivRef();
if (!divElem) {
return;
}
return renderer.renderToCanvas({
artifactContent: artifact || new Uint8Array(0),
format: "vector",
backgroundColor: fill,
container: divElem,
pixelPerPt: 8,
});
};
/// get display layer div
const divElem = displayDivRef();
if (!divElem) {
return;
}
/// we allow empty artifact
if (!artifact?.length) {
divElem!.innerHTML = "";
return;
}
console.log(displayDivRef());
/// render after init
withGlobalRenderer(typst.createTypstRenderer, moduleInitOptions, doRender);
}, [permission, displayDivRef, fill, artifact, format]);
/// --- end: update document --- ///
return <div ref={setDisplayDivRef}></div>;
};
TypstDocument.setWasmModuleInitOptions = (opts: typst.InitOptions) => {
moduleInitOptions = opts;
};
e.g. page.tsx
import { createEffect, createResource, createSignal, onMount } from "solid-js";
import { TypstDocument } from "../lib/TypstDocument";
export default function Typstest() {
const getArtifactData = async () => {
const response = await fetch(
'http://localhost:3000/readme.artifact.sir.in',
).then(response => response.arrayBuffer());
return (new Uint8Array(response));
};
const [a] = createResource(getArtifactData);
return (
<div class="w-full h-full justify-center items-center flex flex-col">
This is for testing the [WIP] typst solidjs component
<TypstDocument fill="#343541" artifact={a()} />
</div>
);
}

最终看到的就是文章开头链接所指的景象了。

©2018-2024 Secirian | CC BY-SA 4.0