[英]How can i plot letter around a fabricjs circle
我在画布上添加了一个圆圈,然后将一些文本包裹在一个圆圈上。 这是我到目前为止所拥有的
var circle = new fabric.Circle({
top: 100,
left: 100,
radius: 100,
fill: '',
stroke: 'green',
});
canvas.add(circle);
var obj = "some text"
for(letter in obj){
var newLetter = new fabric.Text(obj[letter], {
top: 100,
left: 100
});
canvas.add(newLetter);
canvas.renderAll();
}
我已经尝试了一些其他解决方案在网络上发布,但是到目前为止,在Fabric上还无法正常工作。
圆形文字。
我开始回答这个问题,以为那很容易,但是有点丑陋。 我将其回滚到一个简单的版本。
我遇到的问题是基本的,但是没有简单的解决方案。
我有一个完全替代的解决方案,因为它太重了,无法在此处给出答案。 它涉及自定义扫描线渲染(各种GPU破解),因此如果文本质量至关重要,则可以尝试沿着这些线查找内容。
我遇到的问题只是忽略它们而已解决,这始终是一个很好的解决方案。 大声笑
如何在2D画布上渲染圆形文本。
由于无法在一个调用中做到这一点,所以我编写了一个函数,一次渲染每个字符。 我使用ctx.measureText
来获取要绘制的整个字符串的大小,然后将其转换为角度像素大小。 然后对各种选项,对齐,拉伸和方向(镜像)进行一些调整,我一次遍历字符串中的每个字符,使用ctx.measureText
来测量其大小,然后使用ctx.setTransform
来定位旋转并缩放字符,然后调用ctx.fillText()
渲染该字符。
它比ctx.fillText()
方法要慢一点,但是填充文本不能在圆上绘制。
需要一些计算。
锻炼给定半径的像素的角度大小是微不足道的,但是我经常看到做得不正确。 因为Javascript以弧度工作,所以角度像素大小仅为
var angularPixelSize = 1 / radius; // simple
因此,为了锻炼某些文本将在圆或给定半径上占据的角度。
var textWidth = ctx.measureText("Hello").width;
var textAngularWidth = angularPixelSize * textWidth;
锻炼单个角色的大小。
var text = "This is some text";
var index = 2; // which character
var characterWidth = ctx.measureText(text[index]).width;
var characterAngularWidth = angularPixelSize * textWidth;
这样便有了角度大小,现在可以将圆上的文本居中,向右或向左对齐。 有关详细信息,请参见代码段。
然后,您需要一次遍历每个字符来计算转换,渲染文本,为下一个字符移动正确的角度距离,直到完成。
var angle = ?? // the start angle
for(var i = 0; i < text.length; i += 1){ // for each character in the string
var c = text[i]; // get character
// get character angular width
var w = ctx.measureText(c).width * angularPixelSize;
// set the matrix to align the text. See code under next paragraph
...
...
// matrix set
ctx.fillText(c,0,0); // as the matrix set the origin just render at 0,0
angle += w;
}
巧妙的数学部分正在设置转换。 我发现直接使用转换矩阵更容易,这使我无需使用太多转换调用就可以进行缩放等操作。
集变换有6个数字,前两个是x轴的方向,后两个是y轴的方向,后两个是画布原点的平移。
这样就得到了Y轴。 对于每个字符,从圆心向外移动的线需要绘制字符的角度,并减少不对齐(注意减少不消除)的角度宽度,以便我们可以使用字符的中心对其进行对齐。
// assume angle is position and w is character angular width from above code
var xDx = Math.cos(angle + w / 2); // get x part of X axis direction
var xDy = Math.sin(angle + w / 2); // get y part of X axis direction
现在我们有了将成为x轴的归一化向量。 沿该轴从左到右绘制字符。 我一口气构造了矩阵,但下面将对其进行分解。 请注意,我在代码段代码中添加了一个boo boo,因此代码是从前到后的(X为Y,Y为X)。请注意,代码段具有将文本插入两个角度之间的功能,因此我将x轴缩放为允许这个。
// assume scale is how much the text is squashed along its length.
ctx.setTransform(
xDx * scale, xDy * scale, // set the direction and size of a pixel for the X axis
-xDy, xDx, // the direction ot the Y axis is perpendicular so switch x and y
-xDy * radius + x, xdx * radius + y // now set the origin by scaling by radius and translating by the circle center
);
这就是绘制圆形字符串的数学和逻辑。 很抱歉,但我没有使用fabric.js,因此它可能有或没有选择。 但是您可以创建自己的函数,并直接将其渲染到与fabric.js相同的画布上,因为它不排除访问权限。 尽管由于fabric.js不知道状态变化,所以保存和恢复画布状态将是值得的。
以下是在实践中显示以上内容的代码段。 它远非理想,但可以使用现有的canvas 2D API快速完成。 Snippet具有测量和绘图的两个功能以及一些基本用法示例。
function showTextDemo(){ /** Include fullScreenCanvas.js begin **/ var canvas = document.getElementById("canv"); if(canvas !== null){ document.body.removeChild(canvas); } canvas = (function () { // creates a blank image with 2d context canvas = document.createElement("canvas"); canvas.id = "canv"; canvas.width = window.innerWidth; canvas.height = window.innerHeight; canvas.style.position = "absolute"; canvas.style.top = "0px"; canvas.style.left = "0px"; canvas.ctx = canvas.getContext("2d"); document.body.appendChild(canvas); return canvas; } ) (); var ctx = canvas.ctx; /** fullScreenCanvas.js end **/ // measure circle text // ctx: canvas context // text: string of text to measure // x,y: position of center // r: radius in pixels // // returns the size metrics of the text // // width: Pixel width of text // angularWidth : angular width of text in radians // pixelAngularSize : angular width of a pixel in radians var measureCircleText = function(ctx, text, x, y, radius){ var textWidth; // get the width of all the text textWidth = ctx.measureText(text).width; return { width :textWidth, angularWidth : (1 / radius) * textWidth, pixelAngularSize : 1 / radius } } // displays text alon a circle // ctx: canvas context // text: string of text to measure // x,y: position of center // r: radius in pixels // start: angle in radians to start. // [end]: optional. If included text align is ignored and the text is // scalled to fit between start and end; // direction var circleText = function(ctx,text,x,y,radius,start,end,direction){ var i, textWidth, pA, pAS, a, aw, wScale, aligned, dir; // save the current textAlign so that it can be restored at end aligned = ctx.textAlign; dir = direction ? 1 : -1; // get the angular size of a pixel in radians pAS = 1 / radius; // get the width of all the text textWidth = ctx.measureText(text).width; // if end is supplied then fit text between start and end if(end !== undefined){ pA = ((end - start) / textWidth) * dir; wScale = (pA / pAS) * dir; }else{ // if no end is supplied corret start and end for alignment pA = -pAS * dir; wScale = -1 * dir; switch(aligned){ case "center": // if centered move around half width start -= pA * (textWidth / 2); end = start + pA * textWidth; break; case "right": end = start; start -= pA * textWidth; break; case "left": end = start + pA * textWidth; } } // some code to help me test. Left it here incase someone wants to underline // rmove the following 3 lines if you dont need underline ctx.beginPath(); ctx.arc(x,y,radius,end,start,end>start?true:false); ctx.stroke(); ctx.textAlign = "center"; // align for rendering a = start; // set the start angle for (var i = 0; i < text.length; i += 1) { // for each character // get the angular width of the text aw = ctx.measureText(text[i]).width * pA; var xDx = Math.cos(a + aw / 2); // get the yAxies vector from the center x,y out var xDy = Math.sin(a + aw / 2); if (xDy < 0) { // is the text upside down. If it is flip it // sets the transform for each character scaling width if needed ctx.setTransform(-xDy * wScale, xDx * wScale,-xDx,-xDy, xDx * radius + x,xDy * radius + y); }else{ ctx.setTransform(-xDy * wScale, xDx * wScale, xDx, xDy, xDx * radius + x, xDy * radius + y); } // render the character ctx.fillText(text[i],0,0); a += aw; } ctx.setTransform(1,0,0,1,0,0); ctx.textAlign = aligned; } // set up canvas var w = canvas.width; var h = canvas.height; var cw = w / 2; // centers var ch = h / 2; var rad = (h / 2) * 0.9; // radius // clear ctx.clearRect(0, 0, w, h) // the font var fontSize = Math.floor(h/20); if(h < 400){ var fontSize = 10; } ctx.font = fontSize + "px verdana"; // base settings ctx.textAlign = "center"; ctx.textBaseline = "bottom"; ctx.fillStyle = "#666"; ctx.strokeStyle = "#666"; // Text under stretched circleText(ctx, "Test of circular text rendering", cw, ch, rad, Math.PI, 0, true); // Text over stretchered ctx.fillStyle = "Black"; circleText(ctx, "This text is over the top", cw, ch, rad, Math.PI, Math.PI * 2, true); // Show centered text rad -= fontSize + 4; ctx.fillStyle = "Red"; // Use measureCircleText to get angular size var tw = measureCircleText(ctx, "Centered", cw, ch, rad).angularWidth; // centered bottom and top circleText(ctx, "Centered", cw, ch, rad, Math.PI / 2, undefined, true); circleText(ctx, "Centered", cw, ch, rad, -Math.PI * 0.5, undefined, false); // left align bottom and top ctx.textAlign = "left"; circleText(ctx, "Left Align", cw, ch, rad, Math.PI / 2 - tw * 0.6, undefined, true); circleText(ctx, "Left Align Top", cw, ch, rad, -Math.PI / 2 + tw * 0.6, undefined, false); // right align bottom and top ctx.textAlign = "right"; circleText(ctx, "Right Align", cw, ch, rad, Math.PI / 2 + tw * 0.6, undefined, true); circleText(ctx, "Right Align Top", cw, ch, rad, -Math.PI / 2 - tw * 0.6, undefined, false); // Show base line at middle ctx.fillStyle = "blue"; rad -= fontSize + fontSize; ctx.textAlign = "center"; ctx.textBaseline = "middle"; circleText(ctx, "Baseline Middle", cw, ch, rad, Math.PI / 2, undefined, true); circleText(ctx, "Baseline Middle", cw, ch, rad, -Math.PI / 2, undefined, false); // show baseline at top ctx.fillStyle = "Green"; rad -= fontSize + fontSize; ctx.textAlign = "center"; ctx.textBaseline = "top"; circleText(ctx, "Baseline top", cw, ch, rad, Math.PI / 2, undefined, true); circleText(ctx, "Baseline top", cw, ch, rad, -Math.PI / 2, undefined, false); } showTextDemo(); window.addEventListener("resize",showTextDemo);
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.