fabric.js:转换为 SVG 时处理对象文本及其路径

问题描述 投票:0回答:1

我在厨房水槽

使用这个
let drawCirclePath = (cx, cy, r) => {
  return "M" + cx + "," + cy + "m" + -r + ",0a" + r + "," + r + " 0 1,0 " + r * 2 + ",0a" + r + "," + r + " 0 1,0 " + -r * 2 + ",0";
};
fabric.IText.prototype.setCurvedTextPath = function () {
  if (this.curved) {
    let path = new fabric.Path(drawCirclePath(0, 0, this.curveRadius), {
      fill: 'red',
      strokeWidth: 1,
      stroke: 'rgba(0,0,0, 0.1)',
      visible: true
    });

   
    this.set('path', path);
    this.setCurvedTextPosition();
  }
};
fabric.IText.prototype.setCurvedTextPosition = function () {
  if (this.curved && this.path) {
    this.pathSide = this.curveReverse ? 'left' : 'right';
    const offset = this.curveReverse ? Math.PI * this.curveRadius * 2 * 0.25 : Math.PI * this.curveRadius / 2;
    this.pathStartOffset = offset - this.calcTextWidth() / 2;
    this.pathAlign = 'center';
  }
};
var textPath = new fabric.IText('Text on a path', {
    top: 150,
    left: 150,
    curved: true,
    curveRadius: 170,
    curveReverse: false
});
canvas.add(textPath)
textPath.setCurvedTextPath()

var canvas = new fabric.Canvas("canvasFabric");
let drawCirclePath = (cx, cy, r) => {
  return "M" + cx + "," + cy + "m" + -r + ",0a" + r + "," + r + " 0 1,0 " + r * 2 + ",0a" + r + "," + r + " 0 1,0 " + -r * 2 + ",0";
};
fabric.IText.prototype.setCurvedTextPath = function () {
  if (this.curved) {
    let path = new fabric.Path(drawCirclePath(0, 0, this.curveRadius), {
      fill: 'red',
      strokeWidth: 1,
      stroke: 'rgba(0,0,0, 0.1)',
      visible: true
    });

   
    this.set('path', path);
    this.setCurvedTextPosition();
  }
};
fabric.IText.prototype.setCurvedTextPosition = function () {
  if (this.curved && this.path) {
    this.pathSide = this.curveReverse ? 'left' : 'right';
    const offset = this.curveReverse ? Math.PI * this.curveRadius * 2 * 0.25 : Math.PI * this.curveRadius / 2;
    this.pathStartOffset = offset - this.calcTextWidth() / 2;
    this.pathAlign = 'center';
  }
};
var textPath = new fabric.IText('Text on a path', {
    top: 150,
    left: 150,
    curved: true,
    curveRadius: 170,
    curveReverse: true
});
canvas.add(textPath)
textPath.setCurvedTextPath()
canvas,
svg{
  border: 1px solid #ccc;
  overflow:visible;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js"></script>
<canvas id="canvasFabric" width="500" height="500"></canvas>

当我使用 canvas.toSVG() 将其转换为 SVG 时,路径不会在那里处理。我也想在 SVG 中处理这个问题。 已经尝试过这些但没有运气。 我走得最远的是

<svg width="400" height="400" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="
    background: #000000a8;
">

<g transform="matrix(1 0 0 1 156.5 213.5)" style="">

  <path id="curvedPath" 
  style="stroke: rgb(0,0,0); stroke-opacity: 0.1; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(255,0,0); fill-rule: nonzero; opacity: 1;"  transform=" translate(0, 0)" 
  d="M 0 0 M -170 0 C -170 93.88840747123488 -93.88840747123488 170 0 170 C 93.88840747123487 170 170 93.88840747123488 170 0 C 170 -93.88840747123488 93.88840747123488 -170 0 -170 C -93.88840747123487 -170 -170 -93.8884074712349 -170 -2.0818995585505004e-14"
  stroke-linecap="round"
 />

  <text xml:space="preserve" font-family="Times New Roman" font-size="40" font-style="normal" font-weight="normal" style="stroke: none; stroke-width: 1; stroke-dasharray: none; stroke-linecap: butt; stroke-dashoffset: 0; stroke-linejoin: miter; stroke-miterlimit: 4; fill: rgb(0,0,0); fill-rule: nonzero; opacity: 1; white-space: pre;">
    <textPath xlink:href="#curvedPath" startOffset="0" alignment-baseline="middle" dominant-baseline="middle">
      Text on a path
    </textPath>
  </text>
</g>
</svg>

  1. 需要处理 startOffset 和 side(在 Chrome 中不起作用)
javascript svg canvas fabricjs
1个回答
0
投票

根据此未决问题报告:“将路径上的文本导出到 SVG #6958”
导出文本路径显然尚未完全实现(2023 年)。

但是有一些解决方法可以扩展

fabric.Text.prototype.toSVG
原型。

由于 chromium 目前不支持

side
属性,您可以在圆函数中更改
A
(arcto) 命令的扫描参数:

  const drawCirclePath = (cx, cy, r, curveReverse= true) => {
    let sweep = curveReverse ? 0 : 1;
    let d = `
    M ${-r} 0 
    a ${r} ${r} 0 1 ${sweep} ${r * 2} 0 
    a ${r} ${r} 0 1 ${sweep} ${r * -2} 0`;
    return d;
  };

这样,您就可以通过将路径方向从顺时针反转为逆时针来模拟侧面切换。

我想出了这个修复程序,还将 fabcric.js 基线偏移属性转换为 svg

dominant-baseline

/**
 * https://github.com/fabricjs/fabric.js/issues/6958
 */
fabric.Text.prototype.toSVG = function(reviver) {
  let fontFamily = this.fontFamily.replace(/"/g, "'");
  let fontSize = this.fontSize;
  let fontStyle = this.fontStyle;
  let fontWeight = this.fontWeight;
  let fill = this.fill;

  if (this.path) {
    let path = this.path;
    let fillPath = path.fill ? path.fill : "none";
    let strokePath = path.stroke ? path.stroke : "none";
    let strokeWidth = path.strokeWidth ? path.strokeWidth : 0;

    // get path length
    let pathData = this.path.path;
    let pathInfo = fabric.util.getPathSegmentsInfo(pathData);
    let pathLength = pathInfo[pathInfo.length - 1].length;

    // reverse pathdata to emulate side="right"
    if (this.pathSide === "right") {
      // clone pathdata for reversing
      pathData = JSON.parse(JSON.stringify(pathData));
      pathData = reversePathData(pathData);
    }
    // get pathdata d string
    let d = pathData.flat().join(" ");

    let id = Math.random().toString(36).substr(2, 9);
    let dominantbaseline = "auto";
    let pathStartOffset = this.pathStartOffset;
    let dy = 0;

    // translate fabric.js baseline offsets to svg dominant baseline values
    if (this.pathAlign === "center") {
      dominantbaseline = "middle";
    } else if (this.pathAlign === "baseline") {
      dominantbaseline = "auto";
    } else if (this.pathAlign === "ascender") {
      dominantbaseline = "hanging";
    } else if (this.pathAlign === "descender") {
      dominantbaseline = "auto";
      dy = (fontSize / 100) * -22;
    }

    let textAnchor = "start";
    if (this.textAlign == "center") {
      textAnchor = "middle";
      pathStartOffset += pathLength / 2;
    }

    if (this.textAlign == "right") {
      textAnchor = "end";
      pathStartOffset += pathLength;
    }

    // append texpath to defs or as rendered element
    let textPathEl;
    if (
      (fillPath && fillPath !== "none") ||
      (!strokePath && strokePath !== "none")
    ) {
      textPathEl = `<path id="textOnPath${id}" fill="${fillPath}" stroke="${strokePath}" stroke-width="${strokeWidth}" d="${d}" />`;
    } else {
      textPathEl = `<defs>
        <path id="textOnPath${id}" d="${d}" />
      </defs>`;
    }

    return this._createBaseSVGMarkup(
      this.path ?
      [
        textPathEl,
        `<text 
              font-family="${fontFamily.replace(/"/g, "'")}" 
              fill="${fill}"
              font-size="${fontSize}" 
              font-style="${fontStyle}" 
              font-weight="${fontWeight}"
              >
                <textPath text-anchor="${textAnchor}" 
                dominant-baseline="${dominantbaseline}" 
                startOffset="${pathStartOffset}" 
                href="#textOnPath${id}" 
                xlink:href="#textOnPath${id}"> 
                <tspan dy="${dy}">${this.text}</tspan>
                </textPath>
              </text>`
      ] :
      [
        `<text 
            xml:space="preserve" 
            font-family="${fontFamily}" 
            font-size="${fontSize}" 
            font-style="${fontStyle}" 
            font-weight="${fontWeight}" 
            > 
            ${this.addPaintOrder()}
            ${this.text}
            </text>`
      ], {
        reviver: reviver,
        noStyle: true,
        withShadow: true
      }
    );
  } else {
    return this._createBaseSVGMarkup(this._toSVG(), {
      reviver: reviver,
      noStyle: true,
      withShadow: true
    });
  }
};

/**
 * Reverse pathdata
 */
function reversePathData(pathData) {
  // start compiling new path data
  let pathDataNew = [];

  /**
   * Add closing lineto:
   * needed for path reversing or adding points
   */
  const addClosePathLineto = (pathData) => {
    let pathDataL = pathData.length;
    let closed = pathData[pathDataL - 1][0] === "Z";
    let M = pathData[0];
    let [x0, y0] = [M[1], M[2]];
    let lastCom = closed ? pathData[pathDataL - 2] : pathData[pathDataL - 1];
    let lastComL = lastCom.length;
    let [xE, yE] = [lastCom[lastComL - 2], lastCom[lastComL - 1]];
    if (closed && (x0 !== xE || y0 !== yE)) {
      pathData.pop();
      pathData.push(["L", x0, y0], ["Z"]);
    }
    return path;
  };

  // helper to rearrange control points for all command types
  const reverseControlPoints = (values) => {
    let controlPoints = [];
    let endPoint = [];
    for (let p = 0; p < values.length; p += 2) {
      controlPoints.push([values[p], values[p + 1]]);
    }
    endPoint = controlPoints.pop();
    controlPoints.reverse();
    return [controlPoints, endPoint];
  };

  let closed =
    pathData[pathData.length - 1][0].toLowerCase() === "z" ? true : false;
  if (closed) {
    // add lineto closing space between Z and M
    pathData = addClosePathLineto(pathData);
    // remove Z closepath
    pathData.pop();
  }

  // define last point as new M if path isn't closed
  let valuesLast = pathData[pathData.length - 1];
  let valuesLastL = valuesLast.length;
  let M = closed ?
    pathData[0] :
    ["M", valuesLast[valuesLastL - 2], valuesLast[valuesLastL - 1]];
  // starting M stays the same – unless the path is not closed
  pathDataNew.push(M);

  // reverse path data command order for processing
  pathData.reverse();
  for (let i = 1; i < pathData.length; i++) {
    let com = pathData[i];
    let values = com.slice(1);
    let comPrev = pathData[i - 1];
    let typePrev = comPrev[0];
    let valuesPrev = comPrev.slice(1);
    // get reversed control points and new end coordinates
    let [controlPointsPrev, endPointsPrev] = reverseControlPoints(valuesPrev);
    let [controlPoints, endPoints] = reverseControlPoints(values);

    // create new path data
    let newValues = [];
    newValues = controlPointsPrev.flat().concat(endPoints);
    pathDataNew.push([typePrev, ...newValues]);
  }

  // add previously removed Z close path
  if (closed) {
    pathDataNew.push(["z"]);
  }
  return pathDataNew;
}
<h3>Fabric.js</h3>
<canvas id="canvasFabric" width="500" height="500"></canvas>
<h3>Svg output</h3>
<div id="svgOut"> </div>
<script src='https://cdnjs.cloudflare.com/ajax/libs/fabric.js/5.3.1/fabric.min.js'></script>

<script>
  window.addEventListener('DOMContentLoaded', e => {
    var canvas = new fabric.Canvas("canvasFabric");


    const drawCirclePath = (cx, cy, r, reverse=false) => {
      let sweep = reverse ? 1 : 0;
      let d = `M ${-r} 0 a ${r} ${r} 0 1 ${sweep} ${r * 2} 0 a ${r} ${r} 0 1 ${sweep} ${r * -2} 0`;
      return d;
    };


    let d = drawCirclePath(0, 0, 170);
    
    // Initiate a path instance
    let path = new fabric.Path(d, {
      fill: "red",
    });
    
    
    
    
    let textPath = new fabric.IText('Text on a path', {
      path: path,
      top: 50,
      left: 50,
      fontFamily: 'Georgia',
      fontStyle: 'italic',
      fontSize: 50,
      pathSide: "right",
      fill: 'blue',
      pathStartOffset: 0,
      pathAlign: 'ascender',
      textAlign: 'center',
      strokePath: 'none',
      fillPath: 'none'
    });
    //canvas.add(path)
    canvas.add(textPath)
    // return svg
    let svg = canvas.toSVG();
    svgOut.innerHTML = svg;
  })
</script>

© www.soinside.com 2019 - 2024. All rights reserved.