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
相关产品推荐
相关产品推荐

