使用Fabric.js的getScaledWidth()计算自定义字体文本宽度不准确问题
解决node-fabric自定义字体下文本宽度测量偏差问题
问题根源
在Node.js环境中使用node-fabric的自定义字体时,getScaledWidth()返回的宽度不准确,本质是自定义字体未完全加载就进行尺寸测量。fabric.nodeCanvas.registerFont()仅完成字体注册,但系统并未立即将字体加载到渲染上下文,此时fabric会使用 fallback 字体计算尺寸,导致后续实际渲染时用真实字体显示,宽度超出预期。
解决方案
1. 强制触发字体加载后再测量
注册字体后,通过临时Canvas绘制空白字符,让系统完成字体加载,确保后续测量使用真实字体:
const fabric = require('fabric').fabric; // 注册自定义字体 fabric.nodeCanvas.registerFont('./custom-font.ttf', { family: 'CustomFont', weight: 'regular', style: 'normal' }); // 临时画布触发字体加载 const tempCanvas = fabric.nodeCanvas.createCanvas(1, 1); const tempCtx = tempCanvas.getContext('2d'); tempCtx.font = '12px CustomFont'; tempCtx.fillText(' ', 0, 0); // 绘制空白字符,触发字体加载 // 后续创建文本对象、测量尺寸的逻辑 const text_object = new fabric.Text("Username goes here...", { fill: 'red', fontFamily: 'CustomFont', backgroundColor: 'blue' });
2. 修改尺寸测量逻辑,清除fabric缓存
fabric会缓存文本对象的尺寸,修改fontSize后需要清除缓存并重新计算坐标,避免使用旧的测量值:
const maximum_width = 512; let font_size = 300; text_object.set({ fontSize: font_size, angle: 0 }); // 清除缓存并重新计算尺寸 text_object._clearCache(); text_object.setCoords(); while (text_object.getScaledWidth() > maximum_width) { font_size -= 5; text_object.set({ fontSize: font_size }); text_object._clearCache(); text_object.setCoords(); }
3. 绕开fabric,直接用node-canvas测量
使用node-canvas原生的measureText方法获取准确宽度,完全避免fabric的缓存问题:
// 创建临时上下文用于测量 const tempCanvas = fabric.nodeCanvas.createCanvas(1, 1); const tempCtx = tempCanvas.getContext('2d'); function calculateTextWidth(text, fontSize, fontFamily) { tempCtx.font = `${fontSize}px ${fontFamily}`; return tempCtx.measureText(text).width; } // 计算合适的字体大小 let font_size = 300; while (calculateTextWidth("Username goes here...", font_size, 'CustomFont') > maximum_width) { font_size -= 5; } // 应用到文本对象 text_object.set({ fontSize: font_size });
完整修改后的代码示例
const fabric = require('fabric').fabric; const fs = require('fs'); async function generateScaledTextImage() { const font_path = './path/to/your-font.ttf'; const font_family = 'CustomGoogleFont'; const targetText = "Username goes here..."; const maxWidth = 512; const canvasWidth = 1024; const canvasHeight = 256; // 注册字体 fabric.nodeCanvas.registerFont(font_path, { family: font_family, weight: 'regular', style: 'normal' }); // 触发字体加载 const tempCanvas = fabric.nodeCanvas.createCanvas(1, 1); const tempCtx = tempCanvas.getContext('2d'); tempCtx.font = '12px ' + font_family; tempCtx.fillText(' ', 0, 0); // 创建文本对象 const textObj = new fabric.Text(targetText, { fill: 'red', fontFamily: font_family, backgroundColor: 'blue' }); // 计算合适的字体大小(使用原生测量) let fontSize = 300; while (calculateTextWidth(targetText, fontSize, font_family) > maxWidth) { fontSize -= 5; } textObj.set({ fontSize: fontSize }); // 添加到画布并渲染 const canvas = new fabric.StaticCanvas(null, { width: canvasWidth, height: canvasHeight, backgroundColor: 'yellow' }); canvas.add(textObj); canvas.renderAll(); // 导出图片 fs.writeFileSync('output.png', canvas.toBuffer('image/png')); } function calculateTextWidth(text, fontSize, fontFamily) { const tempCanvas = fabric.nodeCanvas.createCanvas(1, 1); const tempCtx = tempCanvas.getContext('2d'); tempCtx.font = `${fontSize}px ${fontFamily}`; return tempCtx.measureText(text).width; } generateScaledTextImage();
内容的提问来源于stack exchange,提问作者Karam Haj
相关产品推荐
相关产品推荐

