Fabric.js Document Engine

搜索文档

查找页面或章节

EN

SVG 导出

查看 Markdown

曲线文字按画布的样子导出,文件不依赖原来的图片链接。

直接用 fabricjs导出svg 有两个问题。沿路径排列的文字位置不对;文件通过 URL 链接图片,链接一失效就只剩空框。引擎的 SVG 导出解决了这两点,其他对象仍然和 Fabric 写出来的完全一样。和其他 canvas导出svg 的做法相比,它不需要额外处理。

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

svg-export-demo.tsx
"use client";
 
import { FabricText, Path } from "fabric";
import { downloadExport } from "fabricjs-document-engine";
import { useDocumentEngine } from "fabricjs-document-engine/react";
import { DownloadIcon, EyeIcon } from "lucide-react";
import { useTranslations } from "next-intl";
import { useEffect, useState } from "react";
 
import {
  COLORS,
  DemoCanvas,
  DemoFrame,
  Status,
  Toolbar,
  ToolButton,
} from "./demo-ui";
import { PAGE, useFabricCanvas } from "./use-fabric-canvas";
 
type Output = "vector" | "fabric";
 
export default function SvgExportDemo() {
  const t = useTranslations("demos");
  const { elementRef, canvas } = useFabricCanvas();
  const engine = useDocumentEngine(canvas);
  const [preview, setPreview] = useState<{
    url: string;
    output: Output;
  } | null>(null);
  const [message, setMessage] = useState("");
  const words = t("svg.words");
 
  // Text that follows a curve, centred on it, with a highlight behind it.
  useEffect(() => {
    if (!engine) return;
    engine.canvas.add(
      new FabricText(words, {
        path: new Path("M 40 230 Q 240 20 440 230", {
          fill: "",
          stroke: "#d4d4d8",
          strokeWidth: 2,
          strokeDashArray: [6, 6],
        }),
        left: PAGE.width / 2,
        top: PAGE.height / 2,
        fontFamily: "sans-serif",
        fontSize: 28,
        fill: "#0d0d0d",
        pathAlign: "center",
        textAlign: "center",
        textBackgroundColor: COLORS[4],
        underline: true,
      })
    );
    engine.clearHistory();
  }, [engine, words]);
 
  // The preview URL belongs to this component; free it when it changes.
  useEffect(
    () => () => {
      if (preview) URL.revokeObjectURL(preview.url);
    },
    [preview]
  );
 
  async function render(output: Output, download: boolean) {
    if (!engine) return;
    const result = await engine.export({
      format: "svg",
      svg: { textOnPath: output, embedImages: true },
    });
    if (download) {
      downloadExport(result, "curved-text.svg");
      setMessage(t("svg.downloaded"));
      return;
    }
    setPreview({ url: URL.createObjectURL(result.blob), output });
    setMessage(
      t(output === "vector" ? "svg.previewVector" : "svg.previewFabric")
    );
  }
 
  return (
    <DemoFrame>
      <DemoCanvas elementRef={elementRef} label={t("canvas")} />
 
      {preview && (
        <figure className="overflow-hidden rounded-lg border bg-white">
          {/* eslint-disable-next-line @next/next/no-img-element -- a blob URL of the export itself */}
          <img
            src={preview.url}
            alt={t("svg.previewAlt")}
            className="block w-full"
          />
        </figure>
      )}
 
      <Toolbar>
        <ToolButton
          icon={DownloadIcon}
          label={t("svg.download")}
          primary
          disabled={!engine}
          onClick={() => void render("vector", true)}
        />
        <ToolButton
          icon={EyeIcon}
          label={t("svg.showVector")}
          disabled={!engine}
          onClick={() => void render("vector", false)}
        />
        <ToolButton
          icon={EyeIcon}
          label={t("svg.showFabric")}
          disabled={!engine}
          onClick={() => void render("fabric", false)}
        />
      </Toolbar>
 
      <Status tone={message ? "ok" : "idle"}>{message || t("svg.hint")}</Status>
    </DemoFrame>
  );
}

曲线文字

Fabric.js 的 SVG 里没有 <textPath>,svg 文字路径是用一个个旋转的字母写出来的。这种输出会忽略 pathAlign,把抬高的字母移向错误的方向,还把文字背景和下划线画成直的。文字里有空格时,Fabric 6 和 7 甚至会写出无效的 XML,浏览器和 Illustrator 都打不开。

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

现在每次 SVG 导出都会把曲线文字的每个字母写在画布绘制它的位置,背景和装饰线也在同一个位置:

  • 沿路径的 pathAlign、pathSide、pathStartOffset 和 textAlign
  • 单个字母的样式、抬高和降低的字母(deltaY)、描边和 paintFirst
  • 文字背景、下划线、上划线和删除线
  • 可见时,路径本身

每个字母都是带变换的普通 <text>,所有 SVG 阅读器都能理解,文字也仍然可以编辑。传入 svg: { textOnPath: "fabric" } 可以保留 Fabric 自己的输出。

嵌入图片和字体

const result = await engine.export({
  format: "svg",
  svg: {
    embedImages: true,
    embedFonts: { "Brand Sans": "/fonts/brand-sans.woff2" },
  },
});
  • embedImages: true 会嵌入图片,把每张图片作为数据放进文件,包括图案填充和编组里的图片。来自其他网站、没有 CORS 的图片会保留为链接,并给出带对象 id 的 IMAGE_NOT_EMBEDDED 警告。
  • 图片无法嵌入时,embedImages: "require" 会以 EXPORT_BLOCKED 阻止导出。
  • maxEmbeddedImageBytes 会略过超过限制的图片(默认 25 MB)。
  • embedFonts 把字体族对应到一个文件:URL 或文件字节。设置在单个字母上的字体也会被嵌入,而 Fabric 自己的 @font-face 输出会漏掉它们,导致换一台电脑打开时字体变形。

按需求选择配置项

每次 fabricjs导出svg 都可以用下面的配置项,按文件的去处来选:

需求配置项
和画布看起来一样的 SVG默认值
给 Illustrator 或其他设备用的文件embedImages: true
在任何电脑上都显示品牌字体embedFonts
用于对比的 Fabric 原始输出textOnPath: "fabric"

编组里有沿路径排列的文字时,给编组设置 objectCaching: false。开启缓存时,Fabric 会在画布上裁掉高出路径的字母,而 SVG 会完整显示它们。更多导出格式见导出,PDF 导出也用了同样的曲线文字修正。