@fraqjs/plugin-takumi
@fraqjs/plugin-takumi 提供了基于 Takumi 的图片渲染服务。
安装与配置
将插件添加到 fraq.yml 的 plugins 字段下:
plugins:
fraqjs/takumi:
# 指定 renderJsx 等方法的默认渲染选项
# 这些选项会与调用时传入的选项进行合并,后者会覆盖前者
# 其中 stylesheets、fonts 和 images 会按顺序追加
# renderDefaults:
# 当注册字体时发生冲突(即尝试注册一个已存在的字体名称)时的处理方法,可选值有:
# - error:抛出错误
# - warn-and-ignore:默认值,发出警告并忽略新的注册
# - warn-and-replace:发出警告并替换已存在的字体
onFontRegisterConflict: warn-and-ignore
# 是否加载 Fraq 提供的内置字体,默认为 true
loadBuiltinFonts: true@fraqjs/plugin-takumi 依赖 @takumi-rs/core 包。Takumi 是一个 Rust 项目,在 Node.js 环境下只支持如下平台:
- Windows x64/arm64
- macOS x64/arm64
- Linux x64/arm64
请确保你的部署环境符合上述要求。
如果你是插件开发者,请将本插件添加到项目的 peerDependencies 中,并在自己的插件中声明依赖:
import { TakumiService } from '@fraqjs/plugin-takumi';
definePlugin({
name: 'my-plugin',
inject: {
takumi: TakumiService,
},
apply(ctx) {
// 使用 ctx.takumi 来访问 TakumiService
},
});此外,为了在编写插件的过程中使用 JSX 语法,你需要:
- 将
react添加为peerDependencies; - 将
@types/react添加为devDependencies; - 在
tsconfig.json中将jsx选项设置为react-jsx; - 使用
.tsx而非.ts作为扩展名。
渲染 JSX 与 HTML
TakumiService 提供了 renderJsx 和 renderHtml 方法,分别用于渲染 JSX 和 HTML:
const img = await ctx.takumi.renderJsx(
<div tw="p-4 bg-gray-100 rounded">Hello, Takumi!</div>,
);
// img: Buffer<ArrayBufferLike>
const img2 = await ctx.takumi.renderHtml(`
<div style="padding: 16px; background-color: #f3f4f6; border-radius: 8px;">
Hello, Takumi!
</div>
`);在使用 renderJsx 时,可以使用 Tailwind CSS 的语法来快速设置样式,例如 tw="p-4 bg-gray-100 rounded";当然,也可以使用 style 属性来直接设置样式。Takumi 支持的样式列表见 “Reference“ 章节。
renderJsx 和 renderHtml 都接受两个参数:第一个参数是需要渲染的内容,第二个参数是渲染选项对象。常用渲染选项包括:
width和height: 图片的宽度和高度,单位为像素。devicePixelRatio: 设备像素比,默认为1,可用于生成高分辨率图片。注意在固定了width和height的情况下,devicePixelRatio不会改变图片的像素尺寸,而是会影响渲染时的缩放比例。signal: 一个AbortSignal,用于中断渲染。
此外,renderJsx 和 renderHtml 还接受 emojiType 选项用于指定 Emoji 风格。默认值为 undefined,表示不处理 Emoji;传入具体风格后,Emoji 会被转换为图片节点,并在渲染前自动准备所需图片资源。可用的 Emoji 风格有:
type EmojiType =
| 'twemoji'
| 'blobmoji'
| 'noto'
| 'openmoji'
| 'fluent'
| 'fluentFlat';为了保持与 Noto Sans SC 字体的风格一致,建议使用 noto 作为 Emoji 风格:
const img = await ctx.takumi.renderJsx(
<div style={{ fontFamily: 'Inter, Noto Sans SC' }}>你好 👋</div>,
{
width: 800,
height: 400,
emojiType: 'noto',
},
);调用时仍然可以通过 images 传入已经获取好的图片资源,它们会和 Emoji 图片资源一起传给 Takumi。
使用字体
@fraqjs/plugin-takumi 内置了多种字体:
Inter:开源的无衬线西文字体。Roboto Mono:开源的等宽西文字体。Noto Sans SC:开源的无衬线简体中文字体。
上述字体均使用 SIL Open Font License 1.1 协议发布,@fraqjs/plugin-takumi 在发布的 npm 包中包含了上述字体的完整 TTF 文件以及 OFL.txt 许可证文件。
由于包含了静态资源,@fraqjs/plugin-takumi 的包体积较大(约 20MB),但我们认为这是合理并且可以接受的,主要原因有:
- 对于机器人的部署环境来说,几十 MB 的体积通常是可以接受的;
- 这些字体文件的体积是无法通过代码优化来减少的;
- 直接包含字体文件可以确保在任何环境下都能获得一致的渲染效果,而无需担心字体文件的加载问题。
你可以在 JSX 的 style 属性中直接使用上述字体:
// 中文字体使用 Noto Sans SC,英文和数字使用 Inter
const img = await ctx.takumi.renderJsx(
<div style={{ fontFamily: 'Inter, Noto Sans SC' }}>你好,T4kum1!</div>,
);也可以在插件加载阶段使用 ctx.takumi.registerFontFamily 来注册自定义字体。注册时可以传入本地字体路径、带有 path 的字体描述对象、字体数据,或远程字体 URL。字体会在渲染时通过 fonts 选项传递给 Takumi,并由渲染器按需加载。
await ctx.takumi.registerFontFamily('Brand Sans', [
'/app/fonts/BrandSans-Regular.ttf',
{
path: '/app/fonts/BrandSans-Bold.ttf',
weight: 700,
},
]);单次渲染需要额外字体时,也可以直接在渲染选项里传入 fonts。远程字体可以使用 URL 字符串:
const img = await ctx.takumi.renderJsx(
<div style={{ fontFamily: 'Example Sans, Inter, Noto Sans SC' }}>Hello!</div>,
{
fonts: ['https://example.com/fonts/ExampleSans.woff2'],
},
);使用 takumi-preview
Fraq 提供了一个名为 @fraqjs/takumi-preview 的工具包,用于在开发过程中预览 Takumi 渲染的效果。你可以将其作为开发依赖安装:
npm install -D @fraqjs/takumi-preview
# or
yarn add -D @fraqjs/takumi-preview
# or
pnpm add -D @fraqjs/takumi-preview为了使用 takumi-preview,你需要将你的卡片模板用 export default 导出,并且在同一个文件里用 previewProps 导出一个对象,指定预览时传递给模板的 props:
// src/templates/MyCard.tsx
export interface MyCardProps {
greetingName: string;
}
export default function MyCard({ greetingName }: MyCardProps) {
return (
<div style={{ fontFamily: 'Inter, Noto Sans SC' }}>
你好, {greetingName}!
</div>
);
}
export const previewProps: MyCardProps = {
greetingName: 'T4kum1',
};随后,你可以在命令行中运行 takumi-preview 来预览渲染效果:
npx takumi-preview src/templates/MyCard.tsxtakumi-preview 会启动一个 HTTP 服务器,你可以在浏览器中访问终端提示的地址(默认为 http://127.0.0.1:4649/)来查看渲染结果。每当你修改了模板文件并保存后,预览页面会自动刷新以显示最新的渲染效果。
预览工具默认使用的渲染选项为 { devicePixelRatio: 1.5 }。你可以通过在模板文件中导出一个名为 previewRenderOptions 的对象来覆盖默认选项:
import type { RenderOptions } from '@takumi-rs/core';
export const previewRenderOptions: RenderOptions = {
width: 800,
height: 600,
devicePixelRatio: 1,
};此外,还可以导出如下的变量或函数:
previewEmojiType:默认为undefined,当不为undefined时,用于指定渲染时使用的 Emoji 风格,会作为emojiType传给renderJsx。previewSetup:一个可选的异步函数,类型为(service: TakumiService) => void | Promise<void>,在预览渲染之前执行,可以在这里对渲染用到的TakumiService实例进行一些额外的配置,例如注册字体等。
加载图像
普通的 renderJsx 和 renderHtml 不会自动从网络获取图片。需要渲染远程图片时,请在渲染选项的 images 中提供图片的 src 和二进制数据;src 必须与 JSX 或 HTML 中的图片地址一致。
const src = 'https://example.com/image.png';
const data = await fetch(src).then((response) => response.arrayBuffer());
const img = await ctx.takumi.renderJsx(<img src={src} alt="Example" />, {
images: [{ src, data }],
});如果同一个模板需要使用多张远程图片,可以使用 @takumi-rs/helpers 提供的 prepareImages 来收集节点中的图片地址并获取资源。fetchCache 可以在多次渲染之间复用图片字节,避免重复请求同一个 URL。
import { prepareImages } from '@takumi-rs/helpers';
import { fromJsx } from '@takumi-rs/helpers/jsx';
const element = (
<div>
<img src="https://example.com/logo.png" alt="Logo" />
<img src="https://example.com/banner.png" alt="Banner" />
</div>
);
const { node } = await fromJsx(element);
const fetchCache = new Map<string, Promise<ArrayBuffer>>();
const images = await prepareImages({ node, fetchCache });
const img = await ctx.takumi.renderJsx(element, { images });