You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

node-canvas结合Fabric.js设置fontWeight无差异问题求助

问题:Fabric.js 设置 fontWeight 为 bold/normal 无视觉差异

我在 node-canvas 中注册了字重为 normal 的字体族,随后在 Fabric.js 的文本对象中分别设置 fontWeight 为 bold 和 normal,但生成的两张图片完全一致,没有任何视觉差异。

原因分析

  • 缺少对应字重的字体文件:仅注册了 normal 字重的字体,当设置 fontWeight: 'bold' 时,系统找不到该字体族对应的bold字重文件,会自动 fallback 到已注册的normal字重字体渲染,因此视觉上无差异。
  • 字体族名不匹配:下载的是Roboto Regular字体,但注册时用的 fontFamily 是 "Press Start 2P Regular",字体名称不匹配会导致渲染逻辑混乱,加重 fallback 行为。

解决方案

1. 注册对应字重的字体文件

如果需要真实的bold效果,必须下载该字体族的bold字重文件,再在node-canvas中注册:

// 注册Roboto Regular(normal字重)
fabric.nodeCanvas.registerFont(__dirname + "/roboto-regular.ttf", {
  family: "Roboto",
  weight: "normal",
});
// 注册Roboto Bold(bold字重)
fabric.nodeCanvas.registerFont(__dirname + "/roboto-bold.ttf", {
  family: "Roboto",
  weight: "bold",
});

同时修改文本对象的 fontFamily 为 "Roboto",确保和注册的字体族名完全一致。

2. 修正字体族名匹配问题

注册字体时的 family 属性必须和文本对象的 fontFamily 保持统一,避免因名称不匹配导致字体无法正确匹配。比如你下载的是Roboto字体,就不要用"Press Start 2P Regular"作为字体族名。

3. 字体合成加粗(不推荐)

如果没有对应bold字体文件,部分渲染引擎会尝试通过算法合成加粗效果,但这种方式的视觉质量远不如原生bold字体,且效果依赖node-canvas的渲染能力,不建议在正式场景使用。

用户原始代码

var fs = require("fs"),
  fabric = require("fabric").fabric,
  imageDataURI = require("image-data-uri"),
  fetch = require("node-fetch");
(async () => {
  const res = await fetch(
    "https://fonts.gstatic.com/s/roboto/v30/KFOmCnqEu92Fr1Me5WZLCzYlKw.ttf",
  );
  const fileStream = fs.createWriteStream("./font.ttf");
  await new Promise((resolve, reject) => {
    res.body.pipe(fileStream);
    res.body.on("error", reject);
    fileStream.on("finish", resolve);
  });
  main();
})();

const main = () => {
  fabric.nodeCanvas.registerFont(__dirname + "/font.ttf", {
    family: "Press Start 2P Regular",
    weight: "normal",
  });

  const textWithBoldFontWeight = {
    objects: [
      {
        type: "textbox",
        version: "5.3.0",
        originX: "left",
        originY: "top",
        left: 323.81,
        top: 208.9,
        width: 323.89,
        height: 79.09999999999998,
        fill: "rgba(255, 255, 255, 1)",
        stroke: "#000",
        strokeWidth: 0,
        strokeDashArray: [],
        strokeLineCap: "butt",
        strokeDashOffset: 0,
        strokeLineJoin: "miter",
        strokeUniform: true,
        strokeMiterLimit: 4,
        scaleX: 0.39,
        scaleY: 0.39,
        angle: 0,
        flipX: false,
        flipY: false,
        opacity: 1,
        visible: true,
        backgroundColor: "",
        fillRule: "nonzero",
        paintFirst: "stroke",
        globalCompositeOperation: "source-over",
        skewX: 0,
        skewY: 0,
        fontFamily: "Press Start 2P Regular",
        fontWeight: "bold",
        fontSize: 70,
        text: "Headline",
        underline: false,
        overline: false,
        linethrough: false,
        textAlign: "center",
        fontStyle: "normal",
        lineHeight: 1.16,
        textBackgroundColor: "",
        charSpacing: 0,
        styles: [],
        direction: "ltr",
        path: null,
        pathStartOffset: 0,
        pathSide: "left",
        pathAlign: "baseline",
        minWidth: 20,
        splitByGrapheme: false,
      },
    ],
    background: "rgba(0, 0, 0, 0)",
  };

  fabric?.Textbox?.fromObject(
    textWithBoldFontWeight.objects[0],
    async function (obj) {
      try {
        const image = obj.toDataURL({
          format: "image/png",
          withoutShadow: false,
        });
        await imageDataURI.outputFile(
          image,
          "./text-with-bold-font-weight.png",
        );
      } catch (error) {
        reject(error);
      }
    },
  );

  const textWithNormalFontWeight = {
    objects: [
      {
        type: "textbox",
        version: "5.3.0",
        originX: "left",
        originY: "top",
        left: 323.81,
        top: 208.9,
        width: 323.89,
        height: 79.09999999999998,
        fill: "rgba(255, 255, 255, 1)",
        stroke: "#000",
        strokeWidth: 0,
        strokeDashArray: [],
        strokeLineCap: "butt",
        strokeDashOffset: 0,
        strokeLineJoin: "miter",
        strokeUniform: true,
        strokeMiterLimit: 4,
        scaleX: 0.39,
        scaleY: 0.39,
        angle: 0,
        flipX: false,
        flipY: false,
        opacity: 1,
        visible: true,
        backgroundColor: "",
        fillRule: "nonzero",
        paintFirst: "stroke",
        globalCompositeOperation: "source-over",
        skewX: 0,
        skewY: 0,
        fontFamily: "Press Start 2P Regular",
        fontWeight: "normal",
        fontSize: 70,
        text: "Headline",
        underline: false,
        overline: false,
        linethrough: false,
        textAlign: "center",
        fontStyle: "normal",
        lineHeight: 1.16,
        textBackgroundColor: "",
        charSpacing: 0,
        styles: [],
        direction: "ltr",
        path: null,
        pathStartOffset: 0,
        pathSide: "left",
        pathAlign: "baseline",
        minWidth: 20,
        splitByGrapheme: false,
      },
    ],
    background: "rgba(0, 0, 0, 0)",
  };

  fabric?.Textbox?.fromObject(
    textWithNormalFontWeight.objects[0],
    async function (obj) {
      try {
        const image = obj.toDataURL({
          format: "image/png",
          withoutShadow: false,
        });
        await imageDataURI.outputFile(
          image,
          "./text-with-normal-font-weight.png",
        );
      } catch (error) {
        reject(error);
      }
    },
  );
};

内容的提问来源于stack exchange,提问作者Shivam gupta

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 05:44:55