Fabric.js Document Engine

Search documentation

Find a page or section

中文

Curved text as the canvas draws it, and files that open without the original image links.

A plain Fabric.js export SVG call has two weak spots. Text on a path comes out in the wrong place, and the file links to its images by URL, so it shows empty boxes once those links stop working. The engine's SVG export fixes both, and every other object stays exactly as Fabric writes it.

Loading the live demo. Its code is below.

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>
  );
}

Curved text

Fabric.js has no <textPath> in its SVG. It writes text on a path as rotated letters, and that output ignores pathAlign, moves raised letters the wrong way, and draws text backgrounds and underlines as straight boxes. When the text contains a space, Fabric 6 and 7 even write invalid XML, which browsers and Illustrator refuse to open.

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

Every SVG export now writes each letter of curved text where the canvas draws it, with its background and decorations in the same place:

  • pathAlign, pathSide, pathStartOffset and textAlign along the path
  • letter styles, raised and lowered letters (deltaY), outlines and paintFirst
  • text backgrounds, underlines, overlines and line-throughs
  • the guide path itself, when it is visible

Each letter is plain <text> with a transform, which every SVG reader understands, and it stays editable text. Pass svg: { textOnPath: "fabric" } to keep Fabric's own output instead.

Embed images and fonts

const result = await engine.export({
  format: "svg",
  svg: {
    embedImages: true,
    embedFonts: { "Brand Sans": "/fonts/brand-sans.woff2" },
  },
});
  • embedImages: true puts every image into the file as data, including pattern fills and images in groups. An image from another site without CORS stays a link, with an IMAGE_NOT_EMBEDDED warning that names the objects.
  • embedImages: "require" blocks the export with EXPORT_BLOCKED instead, when an image cannot be embedded.
  • maxEmbeddedImageBytes leaves out images larger than the limit (25 MB by default).
  • embedFonts maps a font family to a file: a URL or the file's bytes. Fonts set on single letters are embedded too, which Fabric's own @font-face output misses.

When to use which option

Every Fabric.js export SVG call accepts these options, so pick them by where the file is going:

NeedOption
SVG export that looks like the canvasthe defaults
A file for Illustrator or another deviceembedImages: true
Brand fonts on any computerembedFonts
Fabric's own output, for comparisontextOnPath: "fabric"

For text on a path inside a group, set objectCaching: false on the group. With caching on, Fabric clips letters that rise above the path on the canvas, while the SVG shows them. More export formats are on the export page, and the PDF export uses the same curved text fixes.