繁体   English   中英

我如何在Fabric.js圆周围绘制字母

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

圆形文字。

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

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

  • 围绕圆圈的文本可以颠倒过来。 不好看
  • 间距。 由于画布仅提供基本的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); 

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM