Fraqv0.14.0

@fraqjs/plugin-takumi

@fraqjs/plugin-takumi npm version

@fraqjs/plugin-takumi 提供了基于 Takumi 的图片渲染服务。

安装与配置

将插件添加到 fraq.ymlplugins 字段下:

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 提供了 renderJsxrenderHtml 方法,分别用于渲染 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“ 章节

renderJsxrenderHtml 都接受两个参数:第一个参数是需要渲染的内容,第二个参数是渲染选项对象。常用渲染选项包括:

  • widthheight: 图片的宽度和高度,单位为像素。
  • devicePixelRatio: 设备像素比,默认为 1,可用于生成高分辨率图片。注意在固定了 widthheight 的情况下,devicePixelRatio 不会改变图片的像素尺寸,而是会影响渲染时的缩放比例。
  • signal: 一个 AbortSignal,用于中断渲染。

此外,renderJsxrenderHtml 还接受 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

@fraqjs/takumi-preview npm version

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.tsx

takumi-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 实例进行一些额外的配置,例如注册字体等。

加载图像

普通的 renderJsxrenderHtml 不会自动从网络获取图片。需要渲染远程图片时,请在渲染选项的 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 });

On this page