gpt4 book ai didi

javascript - 我如何围绕Fabric.js圈绘制字母

转载 作者:行者123 更新时间:2023-11-28 18:53:01 25 4
gpt4 key购买 nike

我在画布上添加了一个圆圈,然后将一些文本包裹在一个圆圈上。
这是我到目前为止所拥有的

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上还无法正常工作。

最佳答案

圆形文字。

我开始回答这个问题,以为这很容易,但是有点丑陋。我将其回滚到一个简单的版本。

我遇到的问题是基本的,但没有简单的解决方案。


围绕圆圈的文本可以上下颠倒。不好
阅读
间距。因为画布仅提供基本的2D转换,所以我可以
无法分别缩放文本的顶部和底部,从而导致
看起来间距太宽或压扁的文本。


我有一个完全替代的解决方案,因为它太重了,无法在此处给出答案。它涉及自定义扫描线渲染(各种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);

关于javascript - 我如何围绕Fabric.js圈绘制字母,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/34162321/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com