OpenType JS 和 FabricJS 文本转换为路径

Chr*_*s M 5 javascript svg fabricjs opentype-svg-font

我使用 FabricJS 允许用户在浏览器中设计 SVG。当我想要保存时,我尝试使用 OpenType JS 将文本框 (Fabric) 转换为使用 OpenType 的 SVG 路径。

我看到的问题是我的文本框的位置没有转换为添加到画布的新路径。

当我将新路径添加到画布,然后调用 toSVG() 时,它会在我保存的结果 SVG 中消失。

代码:

async function convertTextToPaths() {
        ungroup();
        var _all = canvas.getObjects();
        for(i=0;i<_all.length;i++) {
            var activeObject = _all[i];
            if(activeObject.type=="textbox") {
                const font = await opentype.load('fonts/'+activeObject.fontFamily+'.ttf');
                debugger;
                console.log(activeObject.type, activeObject.left, activeObject.top+activeObject.height, activeObject.fontSize);
                const path = font.getPath(activeObject.text, activeObject.left, activeObject.top+activeObject.height, activeObject.fontSize);
                const outlinetextpath = new fabric.Path(path.toPathData(3));
                activeObject.dirty=true;
                canvas.remove(activeObject);
                canvas.insertAt(outlinetextpath,2);
                canvas.renderAll();
            }
        }
    }
Run Code Online (Sandbox Code Playgroud)

有任何意义或者有人可以分享一些想法吗?

谢谢

her*_*zel 5

Fabric.js 生成的边界框与 opentype.js 生成的边界框不同

\n

您需要根据字体的指标计算一些缩放因子/比率,以实现适当的垂直对齐。

\n

调用font.getPath(string, x, y, fontSize)将“绘制”一条从下到上的路径:

\n

示例:在 x=500, y=250
\n 处绘制文本元素(画布大小:1000\xc3\x97500px;字体系列:Fira Sans;字体大小:100px;)

\n

Fabric.js

\n
let activeObject = new fabric.Textbox(\'Hamburg\', {\n    left: 500,\n    top: 250,\n    fontFamily: \'Fira Sans\',\n    fontSize: 100\n});\n
Run Code Online (Sandbox Code Playgroud)\n

opentype.js

\n
font.getPath(\'Hamburg\', 500, 250, 100)\n
Run Code Online (Sandbox Code Playgroud)\n

Fabric 与 opentype.js

\n

红色:opentype.js 路径;黑色:织物生成的文本框
\n左:top: 250
\n右:object.top+object.height

\n

opentype.js 生成的元素使用字体的基线作为参考点垂直对齐到 250 像素。
\n而fabric.jstextBox根据元素的顶部(边界框)y 坐标来对齐元素。

\n

工作示例

\n

(由于内容安全策略,下载功能将无法在 SO 上运行)

\n

\r\n
\r\n
let activeObject = new fabric.Textbox(\'Hamburg\', {\n    left: 500,\n    top: 250,\n    fontFamily: \'Fira Sans\',\n    fontSize: 100\n});\n
Run Code Online (Sandbox Code Playgroud)\r\n
font.getPath(\'Hamburg\', 500, 250, 100)\n
Run Code Online (Sandbox Code Playgroud)\r\n
const canvas = new fabric.Canvas("canvas");\nconst fontFileUrl = \'https://fonts.gstatic.com/s/firasans/v16/va9E4kDNxMZdWfMOD5Vvl4jO.ttf\';\nconst [textBoxX, textBoxY] = [500, 250];\nconst fontName = "Fira Sans";\nconst fontWeight = 400;\nconst fontSizeCanvas = 100;\nconst textboxString = "Hamburg";\nconst btnDownload = document.querySelector(\'.btn-download\')\n\n\nconvertTextToPaths();\nasync function convertTextToPaths() {\n  //parse font file with opentype.js\n  const font = await opentype.load(fontFileUrl);\n\n  //draw textbox on canvas\n  let activeObject = new fabric.Textbox(textboxString, {\n    left: textBoxX,\n    top: textBoxY,\n    fontFamily: fontName,\n    fontWeight: fontWeight,\n    fontSize: fontSizeCanvas\n  });\n  canvas.add(activeObject);\n\n  // get properties of fabric.js object\n  let [type, string, fontSize] = [activeObject.type, activeObject.text, activeObject.fontSize];\n  let [left, top, height, width] = [activeObject.left, activeObject.top, activeObject.height, activeObject\n    .width\n  ];\n\n  /**\n   * Get metrics and ratios from font\n   * to calculate absolute offset values according to font size \n   */\n  let unitsPerEm = font.unitsPerEm;\n  let ratio = fontSize / unitsPerEm;\n  // font.descender is a negative value - hence Math.abs()\n  let [ascender, descender] = [font.ascender, Math.abs(font.descender)];\n  let ascenderAbs = Math.ceil(ascender * ratio);\n  let descenderAbs = Math.ceil(descender * ratio);\n  let lineHeight = (ascender + descender) * ratio;\n\n  /**\n   * calculate difference between font path bounding box and \n   * canvas bbox (including line height)\n   */\n  let font2CanvasRatio = 1 / lineHeight * height\n  let baselineY = top + ascenderAbs * font2CanvasRatio;\n\n  // Create path object from font\n  path = font.getPath(string, left, baselineY, fontSize);\n  //path = font.getPath(string, left, top, fontSize);\n  let pathData = path.toPathData();\n  // render on canvas\n  const outlinetextpath = new fabric.Path(pathData, {\n    fill: \'red\'\n  });\n  canvas.add(outlinetextpath);\n\n\n  //optional: just for illustration: render center and baseline\n  canvas.add(new fabric.Line([0, 250, 1000, 250], {\n    stroke: \'red\'\n  }));\n  canvas.add(new fabric.Line([0, baselineY, 1000, baselineY], {\n    stroke: \'purple\'\n  }));\n\n\n  // Download/export svg\n  upDateSVGExport(canvas);\n  canvas.on(\'object:modified\', function(e) {\n    //console.log(\'changed\')\n    upDateSVGExport(canvas);\n  });\n\n}\n\nfunction upDateSVGExport(canvas) {\n  let svgOut = canvas.toSVG();\n  let svgbase64 = \'data:image/svg+xml;base64,\' + btoa(svgOut);\n  btnDownload.href = svgbase64;\n}
Run Code Online (Sandbox Code Playgroud)\r\n
\r\n
\r\n

\n

Codepen 示例

\n

怎么运行的:

\n

本质上,我们需要将 Fabrics.js 对象渲染的高度testBox与基于字体规格的理想渲染边界进行比较。
\n首先我们需要获得一些比率来将字体单位转换为像素。
\n最重要的是,我们需要计算一个比率/因子,将相对字体度量值转换为绝对字体大小相关像素值:

\n

1. 字体规格:字体大小与字体单位的比率
\nletunitsPerEm = font.unitsPerEm;\nletratio = fontSize /unitsPerEm;

\n

大多数网络字体的 value 为 1000unitsPerEm。
\n但是,传统的 truetype 字体(因此没有特别针对 Web 使用进行优化)通常每个 em 使用 2048 个单位。

\n

2. 字体规格:上升和下降

\n
// font.descender is a negative value - hence Math.abs()\nlet [ascender, descender] = [font.ascender, Math.abs(font.descender)];\nlet ascenderAbs = Math.ceil(ascender * ratio);\nlet descenderAbs = Math.ceil(descender * ratio);\nlet lineHeight = (ascender + descender) * ratio;\n
Run Code Online (Sandbox Code Playgroud)\n

关于字体的指标,理想的 bBox 的高度为

\n

(ascender + descender) * FontSize2unitsPerEmRatio

\n

3. 字体规格到画布坐标

\n

Fabric.js bBox 稍大 \xe2\x80\x93 因此我们需要比较它们的高度以获得完美的缩放因子。

\n
let font2CanvasRatio = 1 / lineHeight * height   \n
Run Code Online (Sandbox Code Playgroud)\n

现在我们在使用时得到正确的 y 偏移getPath()

\n
let baselineY = top + ascenderAbs * font2CanvasRatio;\npath = font.getPath(string, left, baselineY, fontSize);\n
Run Code Online (Sandbox Code Playgroud)\n