Fabric.js Document Engine

搜索文档

查找页面或章节

EN

竖排文字

查看 Markdown

可编辑的 canvas文字竖排,从上到下书写,各列从右到左排列。

Fabric.js 只能横向绘制文字。fabric 竖排文字从 2013 年起就是一个公开的需求(fabric.js #511),canvas 竖向文字也没有内置的书写模式。VerticalText 为中文、日文和韩文带来可编辑的 canvas文字竖排。

试一试

正在加载在线示例,代码在下方。

vertical-text-demo.tsx
"use client";
 
import {
  type CombineUpright,
  type TextOrientation,
  VerticalText,
} from "fabricjs-document-engine/text";
import {
  DownloadIcon,
  PencilIcon,
  RotateCwIcon,
  SigmaIcon,
} from "lucide-react";
import { useTranslations } from "next-intl";
import { useEffect, useRef, useState } from "react";
 
import { DemoCanvas, DemoFrame, Status, Toolbar, ToolButton } from "./demo-ui";
import { PAGE, useFabricCanvas } from "./use-fabric-canvas";
 
const TEXT = "縦書きの文章、ABCも読める。\n令和8年10月2日\n한국어 세로쓰기";
 
export default function VerticalTextDemo() {
  const t = useTranslations("demos");
  const { elementRef, canvas } = useFabricCanvas();
  const textRef = useRef<VerticalText | null>(null);
  const [orientation, setOrientation] = useState<TextOrientation>("mixed");
  const [combine, setCombine] = useState<CombineUpright>("digits2");
  const [message, setMessage] = useState("");
 
  useEffect(() => {
    if (!canvas) return;
    const text = new VerticalText(TEXT, {
      left: PAGE.width - 40,
      top: 20,
      originX: "right",
      originY: "top",
      fontSize: 24,
      lineHeight: 1.3,
      fill: "#0d0d0d",
      combineUpright: "digits2",
      styles: {
        0: {
          0: { fill: "#6f00ff" },
          1: { fill: "#6f00ff" },
          2: { fill: "#6f00ff" },
        },
      },
    });
    textRef.current = text;
    canvas.add(text);
    canvas.setActiveObject(text);
    canvas.requestRenderAll();
    return () => {
      canvas.remove(text);
    };
  }, [canvas]);
 
  useEffect(() => {
    const text = textRef.current;
    if (!canvas || !text) return;
    text.set({ textOrientation: orientation, combineUpright: combine });
    text.setCoords();
    canvas.requestRenderAll();
  }, [canvas, orientation, combine]);
 
  function edit() {
    const text = textRef.current;
    if (!canvas || !text) return;
    canvas.setActiveObject(text);
    text.enterEditing();
    text.selectAll();
    setMessage(t("vertical.editing"));
  }
 
  function downloadSvg() {
    if (!canvas) return;
    const url = URL.createObjectURL(
      new Blob([canvas.toSVG()], { type: "image/svg+xml" })
    );
    const link = document.createElement("a");
    link.href = url;
    link.download = "vertical-text.svg";
    link.click();
    setTimeout(() => URL.revokeObjectURL(url), 1000);
    setMessage(t("vertical.downloaded"));
  }
 
  return (
    <DemoFrame>
      <DemoCanvas elementRef={elementRef} label={t("canvas")} />
 
      <Toolbar>
        <ToolButton
          icon={PencilIcon}
          label={t("vertical.edit")}
          primary
          disabled={!canvas}
          onClick={edit}
        />
        <ToolButton
          icon={RotateCwIcon}
          label={
            orientation === "mixed"
              ? t("vertical.mixed")
              : t("vertical.upright")
          }
          onClick={() =>
            setOrientation((value) => (value === "mixed" ? "upright" : "mixed"))
          }
        />
        <ToolButton
          icon={SigmaIcon}
          label={
            combine === "digits2"
              ? t("vertical.combined")
              : t("vertical.separate")
          }
          onClick={() =>
            setCombine((value) => (value === "digits2" ? "none" : "digits2"))
          }
        />
        <ToolButton
          icon={DownloadIcon}
          label={t("vertical.svg")}
          disabled={!canvas}
          onClick={downloadSvg}
        />
      </Toolbar>
 
      <Status tone={message ? "ok" : "idle"}>
        {message || t("vertical.hint")}
      </Status>
    </DemoFrame>
  );
}

点击 编辑 后输入文字,或者使用输入法。方向键可以沿着一列移动,也可以移到下一列。

添加竖排文字

import { VerticalText, textObjects } from "fabricjs-document-engine/text";
 
const engine = createDocumentEngine({ canvas, customObjects: textObjects });
 
canvas.add(
  new VerticalText("縦書きの文章、ABCも読める。\n令和8年10月2日", {
    left: 400,
    top: 20,
    fontSize: 28,
    combineUpright: "digits2",
  })
);
  • 每一行变成一列。列内从上到下阅读,各列从右到左排列。
  • 中文、日文和韩文字符直立,每个字占一个方格。
  • 拉丁单词、数字和括号旋转 90°,并保留自己的字距,和日文印刷书籍一样。
  • 句号和顿号位于方格的右上角。
  • combineUpright: "digits2" 会把一位或两位数字并排放进一个方格,也就是日期常用的纵中横。
  • textOrientation: "upright" 会让所有字符直立,包括拉丁字母。

哪些字符直立、哪些字符旋转,遵循 Unicode 的竖排表(UAX #50)。样式、颜色、下划线和文字背景和普通 IText 一样可以使用。canvas文字竖排中的下划线画在列的右侧。

编辑竖排文字

按键光标移动到
↓ 和 ↑本列的下一个和上一个字符
← 和 →下一列和上一列

光标是一条横线,选区覆盖整段列,点击会找到指针下方的字符。中文和日文输入法和普通文本框一样可以使用。

导出

const { blob } = await engine.export({ format: "svg" });

SVG 导出会给每个直立字符和每段旋转文字单独定位,而不是使用 writing-mode,所以 canvas文字竖排在浏览器、Illustrator 和 PDF 中看起来都一样。PDF 导出使用同样的绘制方式;要在 PDF 中输出中文或日文,请在 fonts 中传入字体文件。

配置项列在文字 API里。