我在厨房水槽
使用这个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>
根据此未决问题报告:“将路径上的文本导出到 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>