重构 monorepo 并完善网页端订阅与首页体验
- 迁移为 frontend-web、frontend-electron、backend-web 与 docker 部署结构 - 网页端:订阅门禁二次弹窗、套餐/支付组件化、顶栏分组对齐 - 首页:最近文件与模板库布局优化,缩略图对齐,下载与删除操作 - 新增管理后台、支付与云端文件 API,更新 README 与项目规范 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,164 @@
|
||||
// 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);
|
||||
|
||||
Reference in New Issue
Block a user