@nasser-sw/fabric 7.0.1-beta1 → 7.0.1-beta10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/0 +0 -0
- package/debug/{konva → konva-master}/CHANGELOG.md +2 -1
- package/debug/{konva → konva-master}/README.md +7 -3
- package/debug/{konva → konva-master}/package.json +1 -1
- package/debug/{konva → konva-master}/release.sh +1 -4
- package/debug/{konva → konva-master}/src/Canvas.ts +37 -0
- package/debug/{konva → konva-master}/src/shapes/Text.ts +2 -2
- package/dist/index.js +1853 -288
- package/dist/index.js.map +1 -1
- package/dist/index.min.js +1 -1
- package/dist/index.min.js.map +1 -1
- package/dist/index.min.mjs +1 -1
- package/dist/index.min.mjs.map +1 -1
- package/dist/index.mjs +1853 -288
- package/dist/index.mjs.map +1 -1
- package/dist/index.node.cjs +1853 -288
- package/dist/index.node.cjs.map +1 -1
- package/dist/index.node.mjs +1853 -288
- package/dist/index.node.mjs.map +1 -1
- package/dist/package.json.min.mjs +1 -1
- package/dist/package.json.mjs +1 -1
- package/dist/src/shapes/Line.d.ts +33 -86
- package/dist/src/shapes/Line.d.ts.map +1 -1
- package/dist/src/shapes/Line.min.mjs +1 -1
- package/dist/src/shapes/Line.min.mjs.map +1 -1
- package/dist/src/shapes/Line.mjs +405 -159
- package/dist/src/shapes/Line.mjs.map +1 -1
- package/dist/src/shapes/Polyline.d.ts +7 -0
- package/dist/src/shapes/Polyline.d.ts.map +1 -1
- package/dist/src/shapes/Polyline.min.mjs +1 -1
- package/dist/src/shapes/Polyline.min.mjs.map +1 -1
- package/dist/src/shapes/Polyline.mjs +48 -16
- package/dist/src/shapes/Polyline.mjs.map +1 -1
- package/dist/src/shapes/Text/Text.d.ts +19 -0
- package/dist/src/shapes/Text/Text.d.ts.map +1 -1
- package/dist/src/shapes/Text/Text.min.mjs +1 -1
- package/dist/src/shapes/Text/Text.min.mjs.map +1 -1
- package/dist/src/shapes/Text/Text.mjs +302 -16
- package/dist/src/shapes/Text/Text.mjs.map +1 -1
- package/dist/src/shapes/Textbox.d.ts +43 -1
- package/dist/src/shapes/Textbox.d.ts.map +1 -1
- package/dist/src/shapes/Textbox.min.mjs +1 -1
- package/dist/src/shapes/Textbox.min.mjs.map +1 -1
- package/dist/src/shapes/Textbox.mjs +521 -67
- package/dist/src/shapes/Textbox.mjs.map +1 -1
- package/dist/src/shapes/Triangle.d.ts +27 -2
- package/dist/src/shapes/Triangle.d.ts.map +1 -1
- package/dist/src/shapes/Triangle.min.mjs +1 -1
- package/dist/src/shapes/Triangle.min.mjs.map +1 -1
- package/dist/src/shapes/Triangle.mjs +72 -12
- package/dist/src/shapes/Triangle.mjs.map +1 -1
- package/dist/src/text/examples/arabicTextExample.d.ts +60 -0
- package/dist/src/text/examples/arabicTextExample.d.ts.map +1 -0
- package/dist/src/text/measure.d.ts +9 -0
- package/dist/src/text/measure.d.ts.map +1 -1
- package/dist/src/text/measure.min.mjs +1 -1
- package/dist/src/text/measure.min.mjs.map +1 -1
- package/dist/src/text/measure.mjs +175 -4
- package/dist/src/text/measure.mjs.map +1 -1
- package/dist/src/text/overlayEditor.d.ts.map +1 -1
- package/dist/src/text/overlayEditor.min.mjs +1 -1
- package/dist/src/text/overlayEditor.min.mjs.map +1 -1
- package/dist/src/text/overlayEditor.mjs +155 -9
- package/dist/src/text/overlayEditor.mjs.map +1 -1
- package/dist/src/text/scriptUtils.d.ts +142 -0
- package/dist/src/text/scriptUtils.d.ts.map +1 -0
- package/dist/src/text/scriptUtils.min.mjs +2 -0
- package/dist/src/text/scriptUtils.min.mjs.map +1 -0
- package/dist/src/text/scriptUtils.mjs +212 -0
- package/dist/src/text/scriptUtils.mjs.map +1 -0
- package/dist/src/util/misc/cornerRadius.d.ts +70 -0
- package/dist/src/util/misc/cornerRadius.d.ts.map +1 -0
- package/dist/src/util/misc/cornerRadius.min.mjs +2 -0
- package/dist/src/util/misc/cornerRadius.min.mjs.map +1 -0
- package/dist/src/util/misc/cornerRadius.mjs +181 -0
- package/dist/src/util/misc/cornerRadius.mjs.map +1 -0
- package/dist-extensions/src/shapes/CustomLine.d.ts +10 -0
- package/dist-extensions/src/shapes/CustomLine.d.ts.map +1 -0
- package/dist-extensions/src/shapes/Line.d.ts +33 -86
- package/dist-extensions/src/shapes/Line.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Polyline.d.ts +7 -0
- package/dist-extensions/src/shapes/Polyline.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Text/Text.d.ts +19 -0
- package/dist-extensions/src/shapes/Text/Text.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Textbox.d.ts +43 -1
- package/dist-extensions/src/shapes/Textbox.d.ts.map +1 -1
- package/dist-extensions/src/shapes/Triangle.d.ts +27 -2
- package/dist-extensions/src/shapes/Triangle.d.ts.map +1 -1
- package/dist-extensions/src/text/measure.d.ts +9 -0
- package/dist-extensions/src/text/measure.d.ts.map +1 -1
- package/dist-extensions/src/text/overlayEditor.d.ts.map +1 -1
- package/dist-extensions/src/text/scriptUtils.d.ts +142 -0
- package/dist-extensions/src/text/scriptUtils.d.ts.map +1 -0
- package/dist-extensions/src/util/misc/cornerRadius.d.ts +70 -0
- package/dist-extensions/src/util/misc/cornerRadius.d.ts.map +1 -0
- package/fabric-test-editor.html +3552 -0
- package/fabric-test2.html +647 -0
- package/fabric.ts +182 -182
- package/fonts/STV Bold.ttf +0 -0
- package/fonts/STV Light.ttf +0 -0
- package/fonts/STV Regular.ttf +0 -0
- package/package.json +164 -164
- package/src/shapes/Line.ts +484 -157
- package/src/shapes/Polyline.ts +70 -29
- package/src/shapes/Text/Text.ts +317 -19
- package/src/shapes/Textbox.ts +544 -12
- package/src/shapes/Triangle.spec.ts +76 -0
- package/src/shapes/Triangle.ts +85 -15
- package/src/text/measure.ts +200 -50
- package/src/text/overlayEditor.ts +164 -12
- package/src/util/misc/cornerRadius.spec.ts +141 -0
- package/src/util/misc/cornerRadius.ts +269 -0
- /package/debug/{konva → konva-master}/LICENSE +0 -0
- /package/debug/{konva → konva-master}/gulpfile.mjs +0 -0
- /package/debug/{konva → konva-master}/resources/doc-includes/ContainerParams.txt +0 -0
- /package/debug/{konva → konva-master}/resources/doc-includes/NodeParams.txt +0 -0
- /package/debug/{konva → konva-master}/resources/doc-includes/ShapeParams.txt +0 -0
- /package/debug/{konva → konva-master}/resources/jsdoc.conf.json +0 -0
- /package/debug/{konva → konva-master}/rollup.config.mjs +0 -0
- /package/debug/{konva → konva-master}/src/Animation.ts +0 -0
- /package/debug/{konva → konva-master}/src/BezierFunctions.ts +0 -0
- /package/debug/{konva → konva-master}/src/Container.ts +0 -0
- /package/debug/{konva → konva-master}/src/Context.ts +0 -0
- /package/debug/{konva → konva-master}/src/Core.ts +0 -0
- /package/debug/{konva → konva-master}/src/DragAndDrop.ts +0 -0
- /package/debug/{konva → konva-master}/src/Factory.ts +0 -0
- /package/debug/{konva → konva-master}/src/FastLayer.ts +0 -0
- /package/debug/{konva → konva-master}/src/Global.ts +0 -0
- /package/debug/{konva → konva-master}/src/Group.ts +0 -0
- /package/debug/{konva → konva-master}/src/Layer.ts +0 -0
- /package/debug/{konva → konva-master}/src/Node.ts +0 -0
- /package/debug/{konva → konva-master}/src/PointerEvents.ts +0 -0
- /package/debug/{konva → konva-master}/src/Shape.ts +0 -0
- /package/debug/{konva → konva-master}/src/Stage.ts +0 -0
- /package/debug/{konva → konva-master}/src/Tween.ts +0 -0
- /package/debug/{konva → konva-master}/src/Util.ts +0 -0
- /package/debug/{konva → konva-master}/src/Validators.ts +0 -0
- /package/debug/{konva → konva-master}/src/_CoreInternals.ts +0 -0
- /package/debug/{konva → konva-master}/src/_FullInternals.ts +0 -0
- /package/debug/{konva → konva-master}/src/canvas-backend.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Blur.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Brighten.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Brightness.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Contrast.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Emboss.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Enhance.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Grayscale.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/HSL.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/HSV.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Invert.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Kaleidoscope.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Mask.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Noise.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Pixelate.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Posterize.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/RGB.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/RGBA.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Sepia.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Solarize.ts +0 -0
- /package/debug/{konva → konva-master}/src/filters/Threshold.ts +0 -0
- /package/debug/{konva → konva-master}/src/index.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Arc.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Arrow.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Circle.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Ellipse.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Image.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Label.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Line.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Path.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Rect.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/RegularPolygon.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Ring.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Sprite.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Star.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/TextPath.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Transformer.ts +0 -0
- /package/debug/{konva → konva-master}/src/shapes/Wedge.ts +0 -0
- /package/debug/{konva → konva-master}/src/skia-backend.ts +0 -0
- /package/debug/{konva → konva-master}/src/types.ts +0 -0
- /package/debug/{konva → konva-master}/tsconfig.json +0 -0
- /package/debug/{konva → konva-master}/tsconfig.test.json +0 -0
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Fabric.js 7 Line Test</title>
|
|
7
|
+
<style>
|
|
8
|
+
body {
|
|
9
|
+
margin: 20px;
|
|
10
|
+
font-family: Arial, sans-serif;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
#canvas-container {
|
|
14
|
+
border: 2px solid #ddd;
|
|
15
|
+
display: inline-block;
|
|
16
|
+
margin: 10px 0;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.controls {
|
|
20
|
+
margin: 10px 0;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
button {
|
|
24
|
+
margin: 5px;
|
|
25
|
+
padding: 8px 16px;
|
|
26
|
+
font-size: 14px;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
.info {
|
|
30
|
+
background: #f5f5f5;
|
|
31
|
+
padding: 10px;
|
|
32
|
+
margin: 10px 0;
|
|
33
|
+
border-radius: 4px;
|
|
34
|
+
}
|
|
35
|
+
</style>
|
|
36
|
+
</head>
|
|
37
|
+
<body>
|
|
38
|
+
<h1>Fabric.js 7 Line Rendering Test</h1>
|
|
39
|
+
|
|
40
|
+
<div class="info">
|
|
41
|
+
<strong>Test Instructions:</strong>
|
|
42
|
+
<ol>
|
|
43
|
+
<li>Click "Add Line" to create a line</li>
|
|
44
|
+
<li>Select the line to see endpoint controls</li>
|
|
45
|
+
<li>Drag the endpoint controls (blue circles)</li>
|
|
46
|
+
<li>Observe if the line renders correctly during drag preview</li>
|
|
47
|
+
<li>Check if the line renders correctly after releasing the drag</li>
|
|
48
|
+
</ol>
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<div class="controls">
|
|
52
|
+
<button id="addLineBtn">Add Line</button>
|
|
53
|
+
<button id="addGradientLineBtn">Add Gradient Line</button>
|
|
54
|
+
<button id="clearCanvasBtn">Clear Canvas</button>
|
|
55
|
+
<button id="toggleDebugBtn">Toggle Debug</button>
|
|
56
|
+
<button id="exportPngBtn">Export as PNG</button>
|
|
57
|
+
<button id="exportSvgBtn">Export as SVG</button>
|
|
58
|
+
<button id="saveJsonBtn">Save JSON</button>
|
|
59
|
+
<button id="loadJsonBtn">Load JSON</button>
|
|
60
|
+
<input type="file" id="loadJsonFile" accept=".json" style="display: none;">
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<div style="display: flex; gap: 20px;">
|
|
64
|
+
<div>
|
|
65
|
+
<h3>Fabric.js 7 Implementation</h3>
|
|
66
|
+
<div id="canvas-container">
|
|
67
|
+
<canvas id="canvas" width="800" height="600"></canvas>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<div>
|
|
72
|
+
<h3>Native Canvas 2D (CFX) Implementation</h3>
|
|
73
|
+
<div style="border: 2px solid #ddd; display: inline-block;">
|
|
74
|
+
<canvas id="cfx-canvas" width="800" height="600"></canvas>
|
|
75
|
+
</div>
|
|
76
|
+
<div class="controls">
|
|
77
|
+
<button id="addCfxLineBtn">Add CFX Line</button>
|
|
78
|
+
<button id="clearCfxCanvasBtn">Clear CFX Canvas</button>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<div class="info">
|
|
84
|
+
<strong>Expected Behavior:</strong> Line should render smoothly during endpoint dragging without clipping or positioning issues.
|
|
85
|
+
<br><strong>Problem:</strong> Line may not render correctly in drag preview but appears fine after drag completion.
|
|
86
|
+
</div>
|
|
87
|
+
|
|
88
|
+
<!-- Load Fabric.js 7 from local build -->
|
|
89
|
+
<script src="./dist/index.js"></script>
|
|
90
|
+
|
|
91
|
+
<script>
|
|
92
|
+
// Initialize canvas
|
|
93
|
+
const canvas = new fabric.Canvas('canvas', {
|
|
94
|
+
backgroundColor: 'white',
|
|
95
|
+
selection: true
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
let debugMode = false;
|
|
99
|
+
let lineCounter = 1;
|
|
100
|
+
|
|
101
|
+
// Add Line button
|
|
102
|
+
document.getElementById('addLineBtn').addEventListener('click', () => {
|
|
103
|
+
// Create a line with some default coordinates
|
|
104
|
+
const line = new fabric.Line([100, 100, 300, 200], {
|
|
105
|
+
stroke: '#ff0000',
|
|
106
|
+
strokeWidth: 3,
|
|
107
|
+
strokeLineCap: 'round',
|
|
108
|
+
selectable: true,
|
|
109
|
+
evented: true
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
canvas.add(line);
|
|
113
|
+
canvas.setActiveObject(line);
|
|
114
|
+
canvas.renderAll();
|
|
115
|
+
|
|
116
|
+
console.log('Added line:', line);
|
|
117
|
+
lineCounter++;
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// Add Gradient Line button
|
|
121
|
+
document.getElementById('addGradientLineBtn').addEventListener('click', () => {
|
|
122
|
+
// Line coordinates
|
|
123
|
+
const x1 = 150, y1 = 120, x2 = 450, y2 = 280;
|
|
124
|
+
|
|
125
|
+
// Create a linear gradient that follows the line direction using absolute coordinates
|
|
126
|
+
const gradient = new fabric.Gradient({
|
|
127
|
+
type: 'linear',
|
|
128
|
+
gradientUnits: 'pixels',
|
|
129
|
+
coords: {
|
|
130
|
+
x1: x1,
|
|
131
|
+
y1: y1,
|
|
132
|
+
x2: x2,
|
|
133
|
+
y2: y2
|
|
134
|
+
},
|
|
135
|
+
colorStops: [
|
|
136
|
+
{ offset: 0, color: '#ff0000' },
|
|
137
|
+
{ offset: 0.5, color: '#00ff00' },
|
|
138
|
+
{ offset: 1, color: '#0000ff' }
|
|
139
|
+
]
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
// Create a line with gradient stroke
|
|
143
|
+
const line = new fabric.Line([x1, y1, x2, y2], {
|
|
144
|
+
stroke: gradient,
|
|
145
|
+
strokeWidth: 8,
|
|
146
|
+
strokeLineCap: 'round',
|
|
147
|
+
selectable: true,
|
|
148
|
+
evented: true
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
canvas.add(line);
|
|
152
|
+
canvas.setActiveObject(line);
|
|
153
|
+
canvas.renderAll();
|
|
154
|
+
|
|
155
|
+
console.log('Added gradient line:', line);
|
|
156
|
+
console.log('Gradient object:', gradient);
|
|
157
|
+
console.log('Line coordinates:', { x1, y1, x2, y2 });
|
|
158
|
+
console.log('Gradient coordinates:', gradient.coords);
|
|
159
|
+
lineCounter++;
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
// Clear Canvas button
|
|
163
|
+
document.getElementById('clearCanvasBtn').addEventListener('click', () => {
|
|
164
|
+
canvas.clear();
|
|
165
|
+
canvas.backgroundColor = 'white';
|
|
166
|
+
canvas.renderAll();
|
|
167
|
+
lineCounter = 1;
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Toggle Debug button
|
|
171
|
+
document.getElementById('toggleDebugBtn').addEventListener('click', () => {
|
|
172
|
+
debugMode = !debugMode;
|
|
173
|
+
const button = document.getElementById('toggleDebugBtn');
|
|
174
|
+
button.textContent = debugMode ? 'Disable Debug' : 'Enable Debug';
|
|
175
|
+
|
|
176
|
+
// Toggle debug visualization for all lines
|
|
177
|
+
canvas.getObjects('line').forEach(line => {
|
|
178
|
+
if (line._debugBoundingBox) {
|
|
179
|
+
// Enable/disable debug rendering
|
|
180
|
+
line._showDebug = debugMode;
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
canvas.renderAll();
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Export as PNG button
|
|
188
|
+
document.getElementById('exportPngBtn').addEventListener('click', () => {
|
|
189
|
+
try {
|
|
190
|
+
// Ensure canvas is rendered before export
|
|
191
|
+
canvas.renderAll();
|
|
192
|
+
|
|
193
|
+
// Get the canvas data URL
|
|
194
|
+
const dataURL = canvas.toDataURL({
|
|
195
|
+
format: 'png',
|
|
196
|
+
quality: 1.0,
|
|
197
|
+
multiplier: 1
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Create download link
|
|
201
|
+
const link = document.createElement('a');
|
|
202
|
+
link.download = `fabric-line-export-${Date.now()}.png`;
|
|
203
|
+
link.href = dataURL;
|
|
204
|
+
|
|
205
|
+
// Trigger download
|
|
206
|
+
document.body.appendChild(link);
|
|
207
|
+
link.click();
|
|
208
|
+
document.body.removeChild(link);
|
|
209
|
+
|
|
210
|
+
console.log('PNG export completed successfully');
|
|
211
|
+
} catch (error) {
|
|
212
|
+
console.error('PNG export failed:', error);
|
|
213
|
+
alert('Export failed: ' + error.message);
|
|
214
|
+
}
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
// Export as SVG button
|
|
218
|
+
document.getElementById('exportSvgBtn').addEventListener('click', () => {
|
|
219
|
+
try {
|
|
220
|
+
// Ensure canvas is rendered before export
|
|
221
|
+
canvas.renderAll();
|
|
222
|
+
|
|
223
|
+
// Get the SVG string
|
|
224
|
+
const svgString = canvas.toSVG({
|
|
225
|
+
viewBox: {
|
|
226
|
+
x: 0,
|
|
227
|
+
y: 0,
|
|
228
|
+
width: canvas.getWidth(),
|
|
229
|
+
height: canvas.getHeight()
|
|
230
|
+
},
|
|
231
|
+
width: canvas.getWidth(),
|
|
232
|
+
height: canvas.getHeight()
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// Create blob and download link
|
|
236
|
+
const blob = new Blob([svgString], { type: 'image/svg+xml;charset=utf-8' });
|
|
237
|
+
const url = URL.createObjectURL(blob);
|
|
238
|
+
|
|
239
|
+
const link = document.createElement('a');
|
|
240
|
+
link.download = `fabric-line-export-${Date.now()}.svg`;
|
|
241
|
+
link.href = url;
|
|
242
|
+
|
|
243
|
+
// Trigger download
|
|
244
|
+
document.body.appendChild(link);
|
|
245
|
+
link.click();
|
|
246
|
+
document.body.removeChild(link);
|
|
247
|
+
|
|
248
|
+
// Clean up
|
|
249
|
+
URL.revokeObjectURL(url);
|
|
250
|
+
|
|
251
|
+
console.log('SVG export completed successfully');
|
|
252
|
+
console.log('SVG content preview:', svgString.substring(0, 200) + '...');
|
|
253
|
+
} catch (error) {
|
|
254
|
+
console.error('SVG export failed:', error);
|
|
255
|
+
alert('SVG export failed: ' + error.message);
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
// Canvas event listeners for debugging
|
|
260
|
+
canvas.on('selection:created', (e) => {
|
|
261
|
+
console.log('Selection created:', e.target);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
canvas.on('selection:updated', (e) => {
|
|
265
|
+
console.log('Selection updated:', e.target);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
canvas.on('object:moving', (e) => {
|
|
269
|
+
if (debugMode) {
|
|
270
|
+
console.log('Object moving:', e.target.type, {
|
|
271
|
+
left: e.target.left,
|
|
272
|
+
top: e.target.top,
|
|
273
|
+
x1: e.target.x1,
|
|
274
|
+
y1: e.target.y1,
|
|
275
|
+
x2: e.target.x2,
|
|
276
|
+
y2: e.target.y2
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
canvas.on('object:modified', (e) => {
|
|
282
|
+
console.log('Object modified:', e.target.type, {
|
|
283
|
+
left: e.target.left,
|
|
284
|
+
top: e.target.top,
|
|
285
|
+
x1: e.target.x1,
|
|
286
|
+
y1: e.target.y1,
|
|
287
|
+
x2: e.target.x2,
|
|
288
|
+
y2: e.target.y2
|
|
289
|
+
});
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// Save JSON functionality
|
|
293
|
+
document.getElementById('saveJsonBtn').addEventListener('click', () => {
|
|
294
|
+
try {
|
|
295
|
+
// Get the canvas as JSON
|
|
296
|
+
const canvasJson = canvas.toJSON();
|
|
297
|
+
|
|
298
|
+
// Create a beautiful formatted JSON string
|
|
299
|
+
const jsonString = JSON.stringify(canvasJson, null, 2);
|
|
300
|
+
|
|
301
|
+
// Create a blob and download link
|
|
302
|
+
const blob = new Blob([jsonString], { type: 'application/json' });
|
|
303
|
+
const url = URL.createObjectURL(blob);
|
|
304
|
+
|
|
305
|
+
const link = document.createElement('a');
|
|
306
|
+
link.download = `fabric-canvas-${Date.now()}.json`;
|
|
307
|
+
link.href = url;
|
|
308
|
+
|
|
309
|
+
// Trigger download
|
|
310
|
+
document.body.appendChild(link);
|
|
311
|
+
link.click();
|
|
312
|
+
document.body.removeChild(link);
|
|
313
|
+
|
|
314
|
+
// Clean up
|
|
315
|
+
URL.revokeObjectURL(url);
|
|
316
|
+
|
|
317
|
+
console.log('Canvas saved as JSON:', canvasJson);
|
|
318
|
+
console.log('JSON string length:', jsonString.length);
|
|
319
|
+
} catch (error) {
|
|
320
|
+
console.error('Save JSON failed:', error);
|
|
321
|
+
alert('Save failed: ' + error.message);
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// Load JSON functionality
|
|
326
|
+
document.getElementById('loadJsonBtn').addEventListener('click', () => {
|
|
327
|
+
document.getElementById('loadJsonFile').click();
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
document.getElementById('loadJsonFile').addEventListener('change', (e) => {
|
|
331
|
+
const file = e.target.files[0];
|
|
332
|
+
if (!file) return;
|
|
333
|
+
|
|
334
|
+
const reader = new FileReader();
|
|
335
|
+
reader.onload = function(e) {
|
|
336
|
+
try {
|
|
337
|
+
const jsonData = JSON.parse(e.target.result);
|
|
338
|
+
|
|
339
|
+
console.log('Loading JSON data:', jsonData);
|
|
340
|
+
|
|
341
|
+
// Clear existing canvas
|
|
342
|
+
canvas.clear();
|
|
343
|
+
|
|
344
|
+
// Load the JSON data into the canvas
|
|
345
|
+
canvas.loadFromJSON(jsonData, () => {
|
|
346
|
+
// This callback is called after loading is complete
|
|
347
|
+
canvas.renderAll();
|
|
348
|
+
console.log('Canvas loaded from JSON successfully');
|
|
349
|
+
console.log('Loaded objects:', canvas.getObjects());
|
|
350
|
+
|
|
351
|
+
// Log details about loaded lines
|
|
352
|
+
const lines = canvas.getObjects('line');
|
|
353
|
+
lines.forEach((line, index) => {
|
|
354
|
+
console.log(`Line ${index + 1}:`, {
|
|
355
|
+
type: line.type,
|
|
356
|
+
left: line.left,
|
|
357
|
+
top: line.top,
|
|
358
|
+
x1: line.x1,
|
|
359
|
+
y1: line.y1,
|
|
360
|
+
x2: line.x2,
|
|
361
|
+
y2: line.y2,
|
|
362
|
+
stroke: line.stroke,
|
|
363
|
+
strokeWidth: line.strokeWidth
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// Clear the file input for next use
|
|
369
|
+
e.target.value = '';
|
|
370
|
+
|
|
371
|
+
} catch (error) {
|
|
372
|
+
console.error('Load JSON failed:', error);
|
|
373
|
+
alert('Load failed: ' + error.message);
|
|
374
|
+
}
|
|
375
|
+
};
|
|
376
|
+
|
|
377
|
+
reader.readAsText(file);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// Add initial line for testing
|
|
381
|
+
const initialLine = new fabric.Line([150, 150, 350, 250], {
|
|
382
|
+
stroke: '#2196F3',
|
|
383
|
+
strokeWidth: 4,
|
|
384
|
+
strokeLineCap: 'round',
|
|
385
|
+
selectable: true,
|
|
386
|
+
evented: true
|
|
387
|
+
});
|
|
388
|
+
|
|
389
|
+
canvas.add(initialLine);
|
|
390
|
+
canvas.renderAll();
|
|
391
|
+
|
|
392
|
+
console.log('Fabric.js version:', fabric.version);
|
|
393
|
+
console.log('Initial line added:', initialLine);
|
|
394
|
+
</script>
|
|
395
|
+
|
|
396
|
+
<script>
|
|
397
|
+
// CFX (Native Canvas 2D) Implementation
|
|
398
|
+
class CFXLine {
|
|
399
|
+
constructor(x1, y1, x2, y2, options = {}) {
|
|
400
|
+
this.x1 = x1;
|
|
401
|
+
this.y1 = y1;
|
|
402
|
+
this.x2 = x2;
|
|
403
|
+
this.y2 = y2;
|
|
404
|
+
this.stroke = options.stroke || '#000000';
|
|
405
|
+
this.strokeWidth = options.strokeWidth || 1;
|
|
406
|
+
this.strokeLineCap = options.strokeLineCap || 'butt';
|
|
407
|
+
this.selected = false;
|
|
408
|
+
this.dragging = null; // null, 'p1', 'p2', or 'line'
|
|
409
|
+
this.controlSize = 12;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
draw(ctx) {
|
|
413
|
+
ctx.save();
|
|
414
|
+
ctx.strokeStyle = this.stroke;
|
|
415
|
+
ctx.lineWidth = this.strokeWidth;
|
|
416
|
+
ctx.lineCap = this.strokeLineCap;
|
|
417
|
+
|
|
418
|
+
ctx.beginPath();
|
|
419
|
+
ctx.moveTo(this.x1, this.y1);
|
|
420
|
+
ctx.lineTo(this.x2, this.y2);
|
|
421
|
+
ctx.stroke();
|
|
422
|
+
|
|
423
|
+
// Draw endpoint controls if selected
|
|
424
|
+
if (this.selected) {
|
|
425
|
+
this.drawControls(ctx);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
ctx.restore();
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
drawControls(ctx) {
|
|
432
|
+
ctx.save();
|
|
433
|
+
ctx.fillStyle = '#007bff';
|
|
434
|
+
ctx.strokeStyle = '#ffffff';
|
|
435
|
+
ctx.lineWidth = 2;
|
|
436
|
+
|
|
437
|
+
// Draw p1 control
|
|
438
|
+
ctx.beginPath();
|
|
439
|
+
ctx.arc(this.x1, this.y1, this.controlSize / 2, 0, 2 * Math.PI);
|
|
440
|
+
ctx.fill();
|
|
441
|
+
ctx.stroke();
|
|
442
|
+
|
|
443
|
+
// Draw p2 control
|
|
444
|
+
ctx.beginPath();
|
|
445
|
+
ctx.arc(this.x2, this.y2, this.controlSize / 2, 0, 2 * Math.PI);
|
|
446
|
+
ctx.fill();
|
|
447
|
+
ctx.stroke();
|
|
448
|
+
|
|
449
|
+
ctx.restore();
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
hitTest(x, y) {
|
|
453
|
+
// Test endpoint controls first
|
|
454
|
+
if (this.selected) {
|
|
455
|
+
const dist1 = Math.sqrt((x - this.x1) ** 2 + (y - this.y1) ** 2);
|
|
456
|
+
if (dist1 <= this.controlSize) return 'p1';
|
|
457
|
+
|
|
458
|
+
const dist2 = Math.sqrt((x - this.x2) ** 2 + (y - this.y2) ** 2);
|
|
459
|
+
if (dist2 <= this.controlSize) return 'p2';
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Test line itself (using distance from point to line)
|
|
463
|
+
const A = this.y2 - this.y1;
|
|
464
|
+
const B = this.x1 - this.x2;
|
|
465
|
+
const C = this.x2 * this.y1 - this.x1 * this.y2;
|
|
466
|
+
const distance = Math.abs(A * x + B * y + C) / Math.sqrt(A * A + B * B);
|
|
467
|
+
|
|
468
|
+
if (distance <= this.strokeWidth / 2 + 3) {
|
|
469
|
+
// Check if point is within line segment bounds
|
|
470
|
+
const minX = Math.min(this.x1, this.x2) - 5;
|
|
471
|
+
const maxX = Math.max(this.x1, this.x2) + 5;
|
|
472
|
+
const minY = Math.min(this.y1, this.y2) - 5;
|
|
473
|
+
const maxY = Math.max(this.y1, this.y2) + 5;
|
|
474
|
+
|
|
475
|
+
if (x >= minX && x <= maxX && y >= minY && y <= maxY) {
|
|
476
|
+
return 'line';
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
return null;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
updateEndpoint(endpoint, x, y, shiftKey = false) {
|
|
484
|
+
if (shiftKey) {
|
|
485
|
+
// Snap to 15-degree angles
|
|
486
|
+
const fixedX = endpoint === 'p1' ? this.x2 : this.x1;
|
|
487
|
+
const fixedY = endpoint === 'p1' ? this.y2 : this.y1;
|
|
488
|
+
|
|
489
|
+
const deltaX = x - fixedX;
|
|
490
|
+
const deltaY = y - fixedY;
|
|
491
|
+
const distance = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
|
492
|
+
|
|
493
|
+
if (distance > 0) {
|
|
494
|
+
let angle = Math.atan2(deltaY, deltaX) * (180 / Math.PI);
|
|
495
|
+
const snapIncrement = 15;
|
|
496
|
+
const snappedAngle = Math.round(angle / snapIncrement) * snapIncrement;
|
|
497
|
+
const snappedRadians = snappedAngle * (Math.PI / 180);
|
|
498
|
+
|
|
499
|
+
x = fixedX + Math.cos(snappedRadians) * distance;
|
|
500
|
+
y = fixedY + Math.sin(snappedRadians) * distance;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (endpoint === 'p1') {
|
|
505
|
+
this.x1 = x;
|
|
506
|
+
this.y1 = y;
|
|
507
|
+
} else if (endpoint === 'p2') {
|
|
508
|
+
this.x2 = x;
|
|
509
|
+
this.y2 = y;
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
move(deltaX, deltaY) {
|
|
514
|
+
this.x1 += deltaX;
|
|
515
|
+
this.y1 += deltaY;
|
|
516
|
+
this.x2 += deltaX;
|
|
517
|
+
this.y2 += deltaY;
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// CFX Canvas Setup
|
|
522
|
+
const cfxCanvas = document.getElementById('cfx-canvas');
|
|
523
|
+
const cfxCtx = cfxCanvas.getContext('2d');
|
|
524
|
+
let cfxLines = [];
|
|
525
|
+
let cfxSelectedLine = null;
|
|
526
|
+
let cfxDragStart = null;
|
|
527
|
+
let cfxDragType = null;
|
|
528
|
+
|
|
529
|
+
function cfxRender() {
|
|
530
|
+
cfxCtx.clearRect(0, 0, cfxCanvas.width, cfxCanvas.height);
|
|
531
|
+
cfxCtx.fillStyle = 'white';
|
|
532
|
+
cfxCtx.fillRect(0, 0, cfxCanvas.width, cfxCanvas.height);
|
|
533
|
+
|
|
534
|
+
cfxLines.forEach(line => line.draw(cfxCtx));
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
function cfxGetMousePos(e) {
|
|
538
|
+
const rect = cfxCanvas.getBoundingClientRect();
|
|
539
|
+
return {
|
|
540
|
+
x: e.clientX - rect.left,
|
|
541
|
+
y: e.clientY - rect.top
|
|
542
|
+
};
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// CFX Mouse Events
|
|
546
|
+
cfxCanvas.addEventListener('mousedown', (e) => {
|
|
547
|
+
const pos = cfxGetMousePos(e);
|
|
548
|
+
|
|
549
|
+
// Test all lines for hit
|
|
550
|
+
for (let line of cfxLines) {
|
|
551
|
+
const hit = line.hitTest(pos.x, pos.y);
|
|
552
|
+
if (hit) {
|
|
553
|
+
cfxSelectedLine = line;
|
|
554
|
+
cfxDragType = hit;
|
|
555
|
+
cfxDragStart = pos;
|
|
556
|
+
|
|
557
|
+
// Update selection
|
|
558
|
+
cfxLines.forEach(l => l.selected = (l === line));
|
|
559
|
+
cfxRender();
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// No hit - deselect all
|
|
565
|
+
cfxSelectedLine = null;
|
|
566
|
+
cfxLines.forEach(l => l.selected = false);
|
|
567
|
+
cfxRender();
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
cfxCanvas.addEventListener('mousemove', (e) => {
|
|
571
|
+
if (!cfxSelectedLine || !cfxDragStart) return;
|
|
572
|
+
|
|
573
|
+
const pos = cfxGetMousePos(e);
|
|
574
|
+
const deltaX = pos.x - cfxDragStart.x;
|
|
575
|
+
const deltaY = pos.y - cfxDragStart.y;
|
|
576
|
+
|
|
577
|
+
if (cfxDragType === 'p1') {
|
|
578
|
+
cfxSelectedLine.updateEndpoint('p1', pos.x, pos.y, e.shiftKey);
|
|
579
|
+
} else if (cfxDragType === 'p2') {
|
|
580
|
+
cfxSelectedLine.updateEndpoint('p2', pos.x, pos.y, e.shiftKey);
|
|
581
|
+
} else if (cfxDragType === 'line') {
|
|
582
|
+
cfxSelectedLine.move(deltaX, deltaY);
|
|
583
|
+
cfxDragStart = pos;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
cfxRender();
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
cfxCanvas.addEventListener('mouseup', (e) => {
|
|
590
|
+
cfxDragStart = null;
|
|
591
|
+
cfxDragType = null;
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
// CFX Cursor handling
|
|
595
|
+
cfxCanvas.addEventListener('mousemove', (e) => {
|
|
596
|
+
if (cfxDragStart) return; // Don't change cursor while dragging
|
|
597
|
+
|
|
598
|
+
const pos = cfxGetMousePos(e);
|
|
599
|
+
let cursor = 'default';
|
|
600
|
+
|
|
601
|
+
for (let line of cfxLines) {
|
|
602
|
+
const hit = line.hitTest(pos.x, pos.y);
|
|
603
|
+
if (hit === 'p1' || hit === 'p2') {
|
|
604
|
+
cursor = 'move';
|
|
605
|
+
break;
|
|
606
|
+
} else if (hit === 'line') {
|
|
607
|
+
cursor = 'move';
|
|
608
|
+
break;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
cfxCanvas.style.cursor = cursor;
|
|
613
|
+
});
|
|
614
|
+
|
|
615
|
+
// CFX Controls
|
|
616
|
+
document.getElementById('addCfxLineBtn').addEventListener('click', () => {
|
|
617
|
+
const line = new CFXLine(150, 150, 350, 250, {
|
|
618
|
+
stroke: '#ff0000',
|
|
619
|
+
strokeWidth: 3,
|
|
620
|
+
strokeLineCap: 'round'
|
|
621
|
+
});
|
|
622
|
+
|
|
623
|
+
cfxLines.push(line);
|
|
624
|
+
cfxRender();
|
|
625
|
+
console.log('Added CFX line:', line);
|
|
626
|
+
});
|
|
627
|
+
|
|
628
|
+
document.getElementById('clearCfxCanvasBtn').addEventListener('click', () => {
|
|
629
|
+
cfxLines = [];
|
|
630
|
+
cfxSelectedLine = null;
|
|
631
|
+
cfxRender();
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
// Add initial CFX line
|
|
635
|
+
const initialCfxLine = new CFXLine(150, 150, 350, 250, {
|
|
636
|
+
stroke: '#2196F3',
|
|
637
|
+
strokeWidth: 4,
|
|
638
|
+
strokeLineCap: 'round'
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
cfxLines.push(initialCfxLine);
|
|
642
|
+
cfxRender();
|
|
643
|
+
|
|
644
|
+
console.log('Initial CFX line added:', initialCfxLine);
|
|
645
|
+
</script>
|
|
646
|
+
</body>
|
|
647
|
+
</html>
|