// Fabric.js 扩展类 (function (fabric) { fabric.CurvedText = fabric.util.createClass(fabric.Object, { type: 'curved-text', diameter: 0, kerning: 0, text: '', flipped: false, fill: '#000', fontFamily: 'Times New Roman', fontSize: 24, // in px fontWeight: 'normal', fontStyle: '', // "normal", "italic" or "oblique". cacheProperties: fabric.Object.prototype.cacheProperties.concat('diameter', 'textBackgroundColor', 'kerning', 'flipped', 'fill', 'fontFamily', 'fontSize', 'fontWeight', 'fontStyle', 'strokeStyle', 'strokeWidth'), strokeStyle: null, strokeWidth: 0, initialize: function (text, options) { options || (options = {}); if (typeof text === 'object' && text) { for (let key in text) { this[key] = text[key] } } else { this.text = text; } this.callSuper('initialize', options); this.set('lockUniScaling', true); var canvas = this.getCircularText(); this.cropCanvas(canvas); this.set('width', canvas.width); this.set('height', canvas.height); }, _getFontDeclaration: function () { return [ (fabric.isLikelyNode ? this.fontWeight : this.fontStyle), (fabric.isLikelyNode ? this.fontStyle : this.fontWeight), this.fontSize + 'px', (fabric.isLikelyNode ? ('"' + this.fontFamily + '"') : this.fontFamily) ].join(' '); }, cropCanvas: function (canvas) { var ctx = canvas.getContext('2d'), w = canvas.width, h = canvas.height, pix = { x: [], y: [] }, n, imageData = ctx.getImageData(0, 0, w, h), fn = function (a, b) { return a - b }; for (var y = 0; y < h; y++) { for (var x = 0; x < w; x++) { if (imageData.data[((y * w + x) * 4) + 3] > 0) { pix.x.push(x); pix.y.push(y); } } } // 如果没有找到任何非透明像素,直接返回,不进行裁剪 if (pix.x.length === 0 || pix.y.length === 0) { return; } pix.x.sort(fn); pix.y.sort(fn); n = pix.x.length - 1; w = pix.x[n] - pix.x[0]; h = pix.y[n] - pix.y[0]; // 确保宽度和高度是有效的正数 if (w <= 0 || h <= 0 || isNaN(w) || isNaN(h)) { return; } var cut = ctx.getImageData(pix.x[0], pix.y[0], w, h); canvas.width = w; canvas.height = h; ctx.putImageData(cut, 0, 0); }, getCircularText: function () { var text = this.text, diameter = this.diameter, flipped = this.flipped, kerning = this.kerning, fill = this.fill, inwardFacing = true, startAngle = 0, canvas = fabric.util.createCanvasElement(), ctx = canvas.getContext('2d'), cw, // character-width x, // iterator clockwise = -1; // draw clockwise for aligned right. Else Anticlockwise if (flipped) { // startAngle = 180; inwardFacing = false; } startAngle *= Math.PI / 180; // convert to radians var d = document.createElement('div'); d.style.fontFamily = this.fontFamily; d.style.fontSize = this.fontSize + 'px'; d.style.fontWeight = this.fontWeight; d.style.fontStyle = this.fontStyle; d.textContent = text; document.body.appendChild(d); var textHeight = d.offsetHeight; document.body.removeChild(d); canvas.width = canvas.height = diameter; ctx.font = this._getFontDeclaration(); if (inwardFacing) { text = text.split('').reverse().join('') }; ctx.translate(diameter / 2, diameter / 2); // Move to center startAngle += (Math.PI * !inwardFacing); // Rotate 180 if outward ctx.textBaseline = 'middle'; // Ensure we draw in exact center ctx.textAlign = 'center'; // Ensure we draw in exact center for (x = 0; x < text.length; x++) { cw = ctx.measureText(text[x]).width; startAngle += ((cw + (x == text.length - 1 ? 0 : kerning)) / (diameter / 2 - textHeight)) / 2 * -clockwise; } ctx.rotate(startAngle); for (x = 0; x < text.length; x++) { cw = ctx.measureText(text[x]).width; // half letter ctx.rotate((cw / 2) / (diameter / 2 - textHeight) * clockwise); if (this.strokeStyle && this.strokeWidth) { ctx.strokeStyle = this.strokeStyle; ctx.lineWidth = this.strokeWidth; ctx.miterLimit = 2; ctx.strokeText(text[x], 0, (inwardFacing ? 1 : -1) * (0 - diameter / 2 + textHeight / 2)); } ctx.fillStyle = fill; ctx.fillText(text[x], 0, (inwardFacing ? 1 : -1) * (0 - diameter / 2 + textHeight / 2)); ctx.rotate((cw / 2 + kerning) / (diameter / 2 - textHeight) * clockwise); // rotate half letter } return canvas; }, _set: function (key, value) { switch (key) { case 'scaleX': this.fontSize *= value; this.diameter *= value; this.width *= value; this.scaleX = 1; if (this.width < 1) { this.width = 1; } break; case 'scaleY': this.height *= value; this.scaleY = 1; if (this.height < 1) { this.height = 1; } break; default: this.callSuper('_set', key, value); break; } }, _render: function (ctx) { var canvas = this.getCircularText(); this.cropCanvas(canvas); this.set('width', canvas.width); this.set('height', canvas.height); ctx.drawImage(canvas, -this.width / 2, -this.height / 2, this.width, this.height); this.setCoords(); }, toObject: function (propertiesToInclude) { return this.callSuper('toObject', ['text', 'diameter', 'textBackgroundColor', 'kerning', 'flipped', 'fill', 'fontFamily', 'fontSize', 'fontWeight', 'fontStyle', 'strokeStyle', 'strokeWidth'].concat(propertiesToInclude)); } }); fabric.CurvedText.fromObject = function (object, callback, forceAsync) { return fabric.Object._fromObject('CurvedText', object, callback, forceAsync, 'curved-text'); }; })(typeof fabric !== 'undefined' ? fabric : require('fabric').fabric);