在 SolidJS 中展示 Typsttitle

2024-09-07date
description

先上效果:

传送门

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

Notice
pr已经合并,现在可以直接安装使用

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

安装cli:

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

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

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似乎还没实现。

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;
};
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-2025 Secirian | CC BY-SA 4.0