apexify.js 4.9.28 → 5.0.0
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/README.md +727 -456
- package/dist/cjs/Canvas/ApexPainter.d.ts +96 -145
- package/dist/cjs/Canvas/ApexPainter.d.ts.map +1 -1
- package/dist/cjs/Canvas/ApexPainter.js +1416 -420
- package/dist/cjs/Canvas/ApexPainter.js.map +1 -1
- package/dist/cjs/Canvas/utils/Charts/charts.d.ts +7 -2
- package/dist/cjs/Canvas/utils/Charts/charts.d.ts.map +1 -1
- package/dist/cjs/Canvas/utils/Charts/charts.js +3 -1
- package/dist/cjs/Canvas/utils/Charts/charts.js.map +1 -1
- package/dist/cjs/Canvas/utils/Custom/advancedLines.d.ts +75 -0
- package/dist/cjs/Canvas/utils/Custom/advancedLines.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/Custom/advancedLines.js +263 -0
- package/dist/cjs/Canvas/utils/Custom/advancedLines.js.map +1 -0
- package/dist/cjs/Canvas/utils/Custom/customLines.d.ts +2 -1
- package/dist/cjs/Canvas/utils/Custom/customLines.d.ts.map +1 -1
- package/dist/cjs/Canvas/utils/Custom/customLines.js +73 -6
- package/dist/cjs/Canvas/utils/Custom/customLines.js.map +1 -1
- package/dist/cjs/Canvas/utils/General/batchOperations.d.ts +17 -0
- package/dist/cjs/Canvas/utils/General/batchOperations.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/General/batchOperations.js +88 -0
- package/dist/cjs/Canvas/utils/General/batchOperations.js.map +1 -0
- package/dist/cjs/Canvas/utils/General/general functions.d.ts +25 -3
- package/dist/cjs/Canvas/utils/General/general functions.d.ts.map +1 -1
- package/dist/cjs/Canvas/utils/General/general functions.js +37 -9
- package/dist/cjs/Canvas/utils/General/general functions.js.map +1 -1
- package/dist/cjs/Canvas/utils/General/imageCompression.d.ts +19 -0
- package/dist/cjs/Canvas/utils/General/imageCompression.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/General/imageCompression.js +262 -0
- package/dist/cjs/Canvas/utils/General/imageCompression.js.map +1 -0
- package/dist/cjs/Canvas/utils/General/imageStitching.d.ts +20 -0
- package/dist/cjs/Canvas/utils/General/imageStitching.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/General/imageStitching.js +227 -0
- package/dist/cjs/Canvas/utils/General/imageStitching.js.map +1 -0
- package/dist/cjs/Canvas/utils/Image/imageEffects.d.ts +37 -0
- package/dist/cjs/Canvas/utils/Image/imageEffects.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/Image/imageEffects.js +128 -0
- package/dist/cjs/Canvas/utils/Image/imageEffects.js.map +1 -0
- package/dist/cjs/Canvas/utils/Image/imageMasking.d.ts +67 -0
- package/dist/cjs/Canvas/utils/Image/imageMasking.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/Image/imageMasking.js +276 -0
- package/dist/cjs/Canvas/utils/Image/imageMasking.js.map +1 -0
- package/dist/cjs/Canvas/utils/Image/imageProperties.d.ts.map +1 -1
- package/dist/cjs/Canvas/utils/Image/imageProperties.js +181 -2
- package/dist/cjs/Canvas/utils/Image/imageProperties.js.map +1 -1
- package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts.map +1 -1
- package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.js +16 -8
- package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.js.map +1 -1
- package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.d.ts +33 -0
- package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.d.ts.map +1 -1
- package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.js +237 -32
- package/dist/cjs/Canvas/utils/Texts/enhancedTextRenderer.js.map +1 -1
- package/dist/cjs/Canvas/utils/Texts/textPathRenderer.d.ts +17 -0
- package/dist/cjs/Canvas/utils/Texts/textPathRenderer.d.ts.map +1 -0
- package/dist/cjs/Canvas/utils/Texts/textPathRenderer.js +233 -0
- package/dist/cjs/Canvas/utils/Texts/textPathRenderer.js.map +1 -0
- package/dist/cjs/Canvas/utils/types.d.ts +171 -10
- package/dist/cjs/Canvas/utils/types.d.ts.map +1 -1
- package/dist/cjs/Canvas/utils/types.js.map +1 -1
- package/dist/cjs/Canvas/utils/utils.d.ts +9 -2
- package/dist/cjs/Canvas/utils/utils.d.ts.map +1 -1
- package/dist/cjs/Canvas/utils/utils.js +32 -1
- package/dist/cjs/Canvas/utils/utils.js.map +1 -1
- package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
- package/dist/esm/Canvas/ApexPainter.d.ts +96 -145
- package/dist/esm/Canvas/ApexPainter.d.ts.map +1 -1
- package/dist/esm/Canvas/ApexPainter.js +1416 -420
- package/dist/esm/Canvas/ApexPainter.js.map +1 -1
- package/dist/esm/Canvas/utils/Charts/charts.d.ts +7 -2
- package/dist/esm/Canvas/utils/Charts/charts.d.ts.map +1 -1
- package/dist/esm/Canvas/utils/Charts/charts.js +3 -1
- package/dist/esm/Canvas/utils/Charts/charts.js.map +1 -1
- package/dist/esm/Canvas/utils/Custom/advancedLines.d.ts +75 -0
- package/dist/esm/Canvas/utils/Custom/advancedLines.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/Custom/advancedLines.js +263 -0
- package/dist/esm/Canvas/utils/Custom/advancedLines.js.map +1 -0
- package/dist/esm/Canvas/utils/Custom/customLines.d.ts +2 -1
- package/dist/esm/Canvas/utils/Custom/customLines.d.ts.map +1 -1
- package/dist/esm/Canvas/utils/Custom/customLines.js +73 -6
- package/dist/esm/Canvas/utils/Custom/customLines.js.map +1 -1
- package/dist/esm/Canvas/utils/General/batchOperations.d.ts +17 -0
- package/dist/esm/Canvas/utils/General/batchOperations.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/General/batchOperations.js +88 -0
- package/dist/esm/Canvas/utils/General/batchOperations.js.map +1 -0
- package/dist/esm/Canvas/utils/General/general functions.d.ts +25 -3
- package/dist/esm/Canvas/utils/General/general functions.d.ts.map +1 -1
- package/dist/esm/Canvas/utils/General/general functions.js +37 -9
- package/dist/esm/Canvas/utils/General/general functions.js.map +1 -1
- package/dist/esm/Canvas/utils/General/imageCompression.d.ts +19 -0
- package/dist/esm/Canvas/utils/General/imageCompression.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/General/imageCompression.js +262 -0
- package/dist/esm/Canvas/utils/General/imageCompression.js.map +1 -0
- package/dist/esm/Canvas/utils/General/imageStitching.d.ts +20 -0
- package/dist/esm/Canvas/utils/General/imageStitching.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/General/imageStitching.js +227 -0
- package/dist/esm/Canvas/utils/General/imageStitching.js.map +1 -0
- package/dist/esm/Canvas/utils/Image/imageEffects.d.ts +37 -0
- package/dist/esm/Canvas/utils/Image/imageEffects.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/Image/imageEffects.js +128 -0
- package/dist/esm/Canvas/utils/Image/imageEffects.js.map +1 -0
- package/dist/esm/Canvas/utils/Image/imageMasking.d.ts +67 -0
- package/dist/esm/Canvas/utils/Image/imageMasking.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/Image/imageMasking.js +276 -0
- package/dist/esm/Canvas/utils/Image/imageMasking.js.map +1 -0
- package/dist/esm/Canvas/utils/Image/imageProperties.d.ts.map +1 -1
- package/dist/esm/Canvas/utils/Image/imageProperties.js +181 -2
- package/dist/esm/Canvas/utils/Image/imageProperties.js.map +1 -1
- package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts.map +1 -1
- package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.js +16 -8
- package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.js.map +1 -1
- package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.d.ts +33 -0
- package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.d.ts.map +1 -1
- package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.js +237 -32
- package/dist/esm/Canvas/utils/Texts/enhancedTextRenderer.js.map +1 -1
- package/dist/esm/Canvas/utils/Texts/textPathRenderer.d.ts +17 -0
- package/dist/esm/Canvas/utils/Texts/textPathRenderer.d.ts.map +1 -0
- package/dist/esm/Canvas/utils/Texts/textPathRenderer.js +233 -0
- package/dist/esm/Canvas/utils/Texts/textPathRenderer.js.map +1 -0
- package/dist/esm/Canvas/utils/types.d.ts +171 -10
- package/dist/esm/Canvas/utils/types.d.ts.map +1 -1
- package/dist/esm/Canvas/utils/types.js.map +1 -1
- package/dist/esm/Canvas/utils/utils.d.ts +9 -2
- package/dist/esm/Canvas/utils/utils.d.ts.map +1 -1
- package/dist/esm/Canvas/utils/utils.js +32 -1
- package/dist/esm/Canvas/utils/utils.js.map +1 -1
- package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
- package/lib/Canvas/ApexPainter.ts +1309 -267
- package/lib/Canvas/utils/Charts/charts.ts +16 -7
- package/lib/Canvas/utils/Custom/advancedLines.ts +335 -0
- package/lib/Canvas/utils/Custom/customLines.ts +84 -9
- package/lib/Canvas/utils/General/batchOperations.ts +103 -0
- package/lib/Canvas/utils/General/general functions.ts +85 -41
- package/lib/Canvas/utils/General/imageCompression.ts +316 -0
- package/lib/Canvas/utils/General/imageStitching.ts +252 -0
- package/lib/Canvas/utils/Image/imageEffects.ts +175 -0
- package/lib/Canvas/utils/Image/imageMasking.ts +335 -0
- package/lib/Canvas/utils/Image/imageProperties.ts +207 -2
- package/lib/Canvas/utils/Patterns/enhancedPatternRenderer.ts +455 -444
- package/lib/Canvas/utils/Texts/enhancedTextRenderer.ts +274 -36
- package/lib/Canvas/utils/Texts/textPathRenderer.ts +320 -0
- package/lib/Canvas/utils/types.ts +173 -10
- package/lib/Canvas/utils/utils.ts +49 -2
- package/package.json +69 -34
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
import { createCanvas, loadImage } from '@napi-rs/canvas';
|
|
2
|
-
import { barChart_1, bgConfig, DataItem, KeyBoxConfig, PieDataConfig, PieChartData, LineChartConfig
|
|
2
|
+
import { barChart_1, bgConfig, DataItem, KeyBoxConfig, PieDataConfig, PieChartData, LineChartConfig } from "../types";
|
|
3
3
|
import path from 'path';
|
|
4
4
|
|
|
5
|
+
// Line chart DataPoint interface (different from bar chart DataPoint)
|
|
6
|
+
interface LineDataPoint {
|
|
7
|
+
label: string;
|
|
8
|
+
y: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
5
11
|
|
|
6
12
|
////////////////////////////////////////BAR CHARTS////////////////////////////////////////
|
|
7
13
|
|
|
@@ -50,8 +56,9 @@ export async function verticalBarChart(data: barChart_1) {
|
|
|
50
56
|
}
|
|
51
57
|
}
|
|
52
58
|
|
|
53
|
-
const canvas = createCanvas(
|
|
54
|
-
const ctx = canvas.getContext('2d');
|
|
59
|
+
const canvas = createCanvas(canvasWidth, canvasHeight);
|
|
60
|
+
const ctx = canvas.getContext('2d') as SKRSContext2D;
|
|
61
|
+
if (!ctx) throw new Error("Unable to get 2D context");
|
|
55
62
|
|
|
56
63
|
if (chartData?.bg?.image) {
|
|
57
64
|
ctx.drawImage(img, 0, 0, 800, 600);
|
|
@@ -160,7 +167,9 @@ export async function verticalBarChart(data: barChart_1) {
|
|
|
160
167
|
}
|
|
161
168
|
}
|
|
162
169
|
|
|
163
|
-
|
|
170
|
+
import { SKRSContext2D } from '@napi-rs/canvas';
|
|
171
|
+
|
|
172
|
+
function drawGridLines(ctx: SKRSContext2D, leftMargin: number, topMargin: number, width: number, height: number, xLabels: number[], yLabels: number[], grid: { color?: string; width?: number }) {
|
|
164
173
|
ctx.strokeStyle = grid.color || 'gray';
|
|
165
174
|
ctx.lineWidth = grid.width || 2;
|
|
166
175
|
|
|
@@ -185,7 +194,7 @@ function drawGridLines(ctx: any, leftMargin: number, topMargin: number, width: n
|
|
|
185
194
|
////////////////////////////////////////PIE CHARTS////////////////////////////////////////
|
|
186
195
|
|
|
187
196
|
|
|
188
|
-
function drawPieChart(ctx:
|
|
197
|
+
function drawPieChart(ctx: SKRSContext2D, data: DataItem[], canvasConfig: bgConfig, pieDataConfig: PieDataConfig): void {
|
|
189
198
|
const width = canvasConfig?.width ?? 1200;
|
|
190
199
|
const height = canvasConfig?.height ?? 400;
|
|
191
200
|
const centerX = width / 2 + (pieDataConfig?.x || 0);
|
|
@@ -256,7 +265,7 @@ function drawPieChart(ctx: any, data: DataItem[], canvasConfig: bgConfig, pieDat
|
|
|
256
265
|
}
|
|
257
266
|
}
|
|
258
267
|
|
|
259
|
-
function drawKeys(ctx:
|
|
268
|
+
function drawKeys(ctx: SKRSContext2D, data: DataItem[], pieConfig: { keyBox: KeyBoxConfig; canvas: bgConfig }): void {
|
|
260
269
|
const { bgcolor, width, height, radius, x, y, content } = pieConfig.keyBox || {};
|
|
261
270
|
const keyX = x || 0;
|
|
262
271
|
const keyY = y || 0;
|
|
@@ -320,7 +329,7 @@ export async function pieChart(pieChartData: PieChartData): Promise<Buffer> {
|
|
|
320
329
|
////////////////////////////////////////LINE CHARTS////////////////////////////////////////
|
|
321
330
|
|
|
322
331
|
|
|
323
|
-
export async function lineChart(data: { data:
|
|
332
|
+
export async function lineChart(data: { data: LineDataPoint[][], lineConfig: LineChartConfig }): Promise<Buffer> {
|
|
324
333
|
if (!data || !data.data || !Array.isArray(data.data) || data.data.length === 0 || !data.lineConfig) {
|
|
325
334
|
throw new Error('Invalid data object or missing data properties');
|
|
326
335
|
}
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import { SKRSContext2D, Image, loadImage } from '@napi-rs/canvas';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Draws an arrow at the end of a line
|
|
7
|
+
* @param ctx - Canvas 2D context
|
|
8
|
+
* @param x - Arrow tip X
|
|
9
|
+
* @param y - Arrow tip Y
|
|
10
|
+
* @param angle - Arrow angle in radians
|
|
11
|
+
* @param size - Arrow size
|
|
12
|
+
* @param style - Arrow style
|
|
13
|
+
* @param color - Arrow color
|
|
14
|
+
*/
|
|
15
|
+
export function drawArrow(
|
|
16
|
+
ctx: SKRSContext2D,
|
|
17
|
+
x: number,
|
|
18
|
+
y: number,
|
|
19
|
+
angle: number,
|
|
20
|
+
size: number,
|
|
21
|
+
style: 'filled' | 'outline',
|
|
22
|
+
color: string
|
|
23
|
+
): void {
|
|
24
|
+
ctx.save();
|
|
25
|
+
ctx.translate(x, y);
|
|
26
|
+
ctx.rotate(angle);
|
|
27
|
+
|
|
28
|
+
const arrowHeadLength = size;
|
|
29
|
+
const arrowHeadWidth = size * 0.6;
|
|
30
|
+
|
|
31
|
+
ctx.beginPath();
|
|
32
|
+
ctx.moveTo(0, 0);
|
|
33
|
+
ctx.lineTo(-arrowHeadLength, -arrowHeadWidth);
|
|
34
|
+
ctx.lineTo(-arrowHeadLength * 0.7, 0);
|
|
35
|
+
ctx.lineTo(-arrowHeadLength, arrowHeadWidth);
|
|
36
|
+
ctx.closePath();
|
|
37
|
+
|
|
38
|
+
if (style === 'filled') {
|
|
39
|
+
ctx.fillStyle = color;
|
|
40
|
+
ctx.fill();
|
|
41
|
+
} else {
|
|
42
|
+
ctx.strokeStyle = color;
|
|
43
|
+
ctx.lineWidth = 2;
|
|
44
|
+
ctx.stroke();
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
ctx.restore();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Draws a marker on a path
|
|
52
|
+
* @param ctx - Canvas 2D context
|
|
53
|
+
* @param x - Marker X position
|
|
54
|
+
* @param y - Marker Y position
|
|
55
|
+
* @param shape - Marker shape
|
|
56
|
+
* @param size - Marker size
|
|
57
|
+
* @param color - Marker color
|
|
58
|
+
*/
|
|
59
|
+
export function drawMarker(
|
|
60
|
+
ctx: SKRSContext2D,
|
|
61
|
+
x: number,
|
|
62
|
+
y: number,
|
|
63
|
+
shape: 'circle' | 'square' | 'diamond' | 'arrow',
|
|
64
|
+
size: number,
|
|
65
|
+
color: string
|
|
66
|
+
): void {
|
|
67
|
+
ctx.save();
|
|
68
|
+
ctx.fillStyle = color;
|
|
69
|
+
ctx.translate(x, y);
|
|
70
|
+
|
|
71
|
+
switch (shape) {
|
|
72
|
+
case 'circle':
|
|
73
|
+
ctx.beginPath();
|
|
74
|
+
ctx.arc(0, 0, size / 2, 0, Math.PI * 2);
|
|
75
|
+
ctx.fill();
|
|
76
|
+
break;
|
|
77
|
+
|
|
78
|
+
case 'square':
|
|
79
|
+
ctx.fillRect(-size / 2, -size / 2, size, size);
|
|
80
|
+
break;
|
|
81
|
+
|
|
82
|
+
case 'diamond':
|
|
83
|
+
ctx.beginPath();
|
|
84
|
+
ctx.moveTo(0, -size / 2);
|
|
85
|
+
ctx.lineTo(size / 2, 0);
|
|
86
|
+
ctx.lineTo(0, size / 2);
|
|
87
|
+
ctx.lineTo(-size / 2, 0);
|
|
88
|
+
ctx.closePath();
|
|
89
|
+
ctx.fill();
|
|
90
|
+
break;
|
|
91
|
+
|
|
92
|
+
case 'arrow':
|
|
93
|
+
ctx.beginPath();
|
|
94
|
+
ctx.moveTo(0, -size / 2);
|
|
95
|
+
ctx.lineTo(size / 2, size / 2);
|
|
96
|
+
ctx.lineTo(-size / 2, size / 2);
|
|
97
|
+
ctx.closePath();
|
|
98
|
+
ctx.fill();
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
ctx.restore();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Creates a smooth path from points
|
|
107
|
+
* @param ctx - Canvas 2D context
|
|
108
|
+
* @param points - Array of points
|
|
109
|
+
* @param tension - Smoothness (0-1)
|
|
110
|
+
* @param closed - Whether path is closed
|
|
111
|
+
*/
|
|
112
|
+
export function createSmoothPath(
|
|
113
|
+
ctx: SKRSContext2D,
|
|
114
|
+
points: Array<{ x: number; y: number }>,
|
|
115
|
+
tension: number = 0.5,
|
|
116
|
+
closed: boolean = false
|
|
117
|
+
): void {
|
|
118
|
+
if (points.length < 2) return;
|
|
119
|
+
|
|
120
|
+
ctx.beginPath();
|
|
121
|
+
ctx.moveTo(points[0].x, points[0].y);
|
|
122
|
+
|
|
123
|
+
if (points.length === 2) {
|
|
124
|
+
ctx.lineTo(points[1].x, points[1].y);
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
for (let i = 0; i < points.length - 1; i++) {
|
|
129
|
+
const p0 = i > 0 ? points[i - 1] : (closed ? points[points.length - 1] : points[i]);
|
|
130
|
+
const p1 = points[i];
|
|
131
|
+
const p2 = points[i + 1];
|
|
132
|
+
const p3 = i < points.length - 2 ? points[i + 2] : (closed ? points[0] : p2);
|
|
133
|
+
|
|
134
|
+
const cp1x = p1.x + (p2.x - p0.x) / 6 * tension;
|
|
135
|
+
const cp1y = p1.y + (p2.y - p0.y) / 6 * tension;
|
|
136
|
+
const cp2x = p2.x - (p3.x - p1.x) / 6 * tension;
|
|
137
|
+
const cp2y = p2.y - (p3.y - p1.y) / 6 * tension;
|
|
138
|
+
|
|
139
|
+
ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, p2.x, p2.y);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (closed) {
|
|
143
|
+
ctx.closePath();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Creates a Catmull-Rom spline path
|
|
149
|
+
* @param ctx - Canvas 2D context
|
|
150
|
+
* @param points - Array of points
|
|
151
|
+
* @param tension - Tension (0-1)
|
|
152
|
+
* @param closed - Whether path is closed
|
|
153
|
+
*/
|
|
154
|
+
export function createCatmullRomPath(
|
|
155
|
+
ctx: SKRSContext2D,
|
|
156
|
+
points: Array<{ x: number; y: number }>,
|
|
157
|
+
tension: number = 0.5,
|
|
158
|
+
closed: boolean = false
|
|
159
|
+
): void {
|
|
160
|
+
if (points.length < 2) return;
|
|
161
|
+
|
|
162
|
+
ctx.beginPath();
|
|
163
|
+
ctx.moveTo(points[0].x, points[0].y);
|
|
164
|
+
|
|
165
|
+
if (points.length === 2) {
|
|
166
|
+
ctx.lineTo(points[1].x, points[1].y);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const segments = closed ? points.length : points.length - 1;
|
|
171
|
+
|
|
172
|
+
for (let i = 0; i < segments; i++) {
|
|
173
|
+
const p0 = closed && i === 0 ? points[points.length - 1] : (i > 0 ? points[i - 1] : points[i]);
|
|
174
|
+
const p1 = points[i];
|
|
175
|
+
const p2 = points[(i + 1) % points.length];
|
|
176
|
+
const p3 = closed && i === segments - 1 ? points[0] : (i < points.length - 2 ? points[i + 2] : p2);
|
|
177
|
+
|
|
178
|
+
for (let t = 0; t <= 1; t += 0.1) {
|
|
179
|
+
const point = catmullRom(p0, p1, p2, p3, t, tension);
|
|
180
|
+
if (t === 0) {
|
|
181
|
+
ctx.moveTo(point.x, point.y);
|
|
182
|
+
} else {
|
|
183
|
+
ctx.lineTo(point.x, point.y);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
if (closed) {
|
|
189
|
+
ctx.closePath();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Catmull-Rom interpolation
|
|
195
|
+
*/
|
|
196
|
+
function catmullRom(
|
|
197
|
+
p0: { x: number; y: number },
|
|
198
|
+
p1: { x: number; y: number },
|
|
199
|
+
p2: { x: number; y: number },
|
|
200
|
+
p3: { x: number; y: number },
|
|
201
|
+
t: number,
|
|
202
|
+
tension: number
|
|
203
|
+
): { x: number; y: number } {
|
|
204
|
+
const t2 = t * t;
|
|
205
|
+
const t3 = t2 * t;
|
|
206
|
+
|
|
207
|
+
const x = 0.5 * (
|
|
208
|
+
(2 * p1.x) +
|
|
209
|
+
(-p0.x + p2.x) * t +
|
|
210
|
+
(2 * p0.x - 5 * p1.x + 4 * p2.x - p3.x) * t2 +
|
|
211
|
+
(-p0.x + 3 * p1.x - 3 * p2.x + p3.x) * t3
|
|
212
|
+
) * tension;
|
|
213
|
+
|
|
214
|
+
const y = 0.5 * (
|
|
215
|
+
(2 * p1.y) +
|
|
216
|
+
(-p0.y + p2.y) * t +
|
|
217
|
+
(2 * p0.y - 5 * p1.y + 4 * p2.y - p3.y) * t2 +
|
|
218
|
+
(-p0.y + 3 * p1.y - 3 * p2.y + p3.y) * t3
|
|
219
|
+
) * tension;
|
|
220
|
+
|
|
221
|
+
return { x: p1.x + x, y: p1.y + y };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Applies line pattern
|
|
226
|
+
* @param ctx - Canvas 2D context
|
|
227
|
+
* @param pattern - Pattern configuration
|
|
228
|
+
*/
|
|
229
|
+
export function applyLinePattern(
|
|
230
|
+
ctx: SKRSContext2D,
|
|
231
|
+
pattern: {
|
|
232
|
+
type: 'dots' | 'dashes' | 'custom';
|
|
233
|
+
segments?: number[];
|
|
234
|
+
offset?: number;
|
|
235
|
+
}
|
|
236
|
+
): void {
|
|
237
|
+
switch (pattern.type) {
|
|
238
|
+
case 'dots':
|
|
239
|
+
ctx.setLineDash([2, 4]);
|
|
240
|
+
break;
|
|
241
|
+
|
|
242
|
+
case 'dashes':
|
|
243
|
+
ctx.setLineDash([10, 5]);
|
|
244
|
+
break;
|
|
245
|
+
|
|
246
|
+
case 'custom':
|
|
247
|
+
if (pattern.segments && pattern.segments.length > 0) {
|
|
248
|
+
ctx.setLineDash(pattern.segments);
|
|
249
|
+
}
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (pattern.offset !== undefined) {
|
|
254
|
+
ctx.lineDashOffset = pattern.offset;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Applies texture to line
|
|
260
|
+
* @param ctx - Canvas 2D context
|
|
261
|
+
* @param textureSource - Texture image source
|
|
262
|
+
* @param lineWidth - Line width
|
|
263
|
+
* @param lineLength - Approximate line length
|
|
264
|
+
*/
|
|
265
|
+
export async function applyLineTexture(
|
|
266
|
+
ctx: SKRSContext2D,
|
|
267
|
+
textureSource: string | Buffer,
|
|
268
|
+
lineWidth: number,
|
|
269
|
+
lineLength: number
|
|
270
|
+
): Promise<void> {
|
|
271
|
+
try {
|
|
272
|
+
let textureImage: Image;
|
|
273
|
+
if (Buffer.isBuffer(textureSource)) {
|
|
274
|
+
textureImage = await loadImage(textureSource);
|
|
275
|
+
} else if (textureSource.startsWith('http')) {
|
|
276
|
+
textureImage = await loadImage(textureSource);
|
|
277
|
+
} else {
|
|
278
|
+
const texturePath = path.join(process.cwd(), textureSource);
|
|
279
|
+
textureImage = await loadImage(fs.readFileSync(texturePath));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// Create pattern from texture
|
|
283
|
+
const pattern = ctx.createPattern(textureImage, 'repeat');
|
|
284
|
+
if (pattern) {
|
|
285
|
+
ctx.strokeStyle = pattern;
|
|
286
|
+
}
|
|
287
|
+
} catch (error) {
|
|
288
|
+
console.error('Error applying line texture:', error);
|
|
289
|
+
// Fallback to current stroke style
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Gets points along a path for marker placement
|
|
295
|
+
* @param points - Path points
|
|
296
|
+
* @param position - Position along path (0-1)
|
|
297
|
+
*/
|
|
298
|
+
export function getPointOnLinePath(
|
|
299
|
+
points: Array<{ x: number; y: number }>,
|
|
300
|
+
position: number
|
|
301
|
+
): { x: number; y: number } {
|
|
302
|
+
if (points.length < 2) return points[0] || { x: 0, y: 0 };
|
|
303
|
+
|
|
304
|
+
// Calculate total path length
|
|
305
|
+
let totalLength = 0;
|
|
306
|
+
const segmentLengths: number[] = [];
|
|
307
|
+
|
|
308
|
+
for (let i = 0; i < points.length - 1; i++) {
|
|
309
|
+
const dx = points[i + 1].x - points[i].x;
|
|
310
|
+
const dy = points[i + 1].y - points[i].y;
|
|
311
|
+
const length = Math.sqrt(dx * dx + dy * dy);
|
|
312
|
+
segmentLengths.push(length);
|
|
313
|
+
totalLength += length;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Find target position
|
|
317
|
+
const targetLength = totalLength * Math.max(0, Math.min(1, position));
|
|
318
|
+
let currentLength = 0;
|
|
319
|
+
|
|
320
|
+
for (let i = 0; i < segmentLengths.length; i++) {
|
|
321
|
+
if (currentLength + segmentLengths[i] >= targetLength) {
|
|
322
|
+
const segmentT = (targetLength - currentLength) / segmentLengths[i];
|
|
323
|
+
const dx = points[i + 1].x - points[i].x;
|
|
324
|
+
const dy = points[i + 1].y - points[i].y;
|
|
325
|
+
return {
|
|
326
|
+
x: points[i].x + dx * segmentT,
|
|
327
|
+
y: points[i].y + dy * segmentT
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
currentLength += segmentLengths[i];
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return points[points.length - 1];
|
|
334
|
+
}
|
|
335
|
+
|
|
@@ -1,15 +1,25 @@
|
|
|
1
|
+
import { SKRSContext2D } from '@napi-rs/canvas';
|
|
1
2
|
import { createGradientFill } from "../Image/imageProperties";
|
|
2
3
|
import { CustomOptions } from "../types";
|
|
4
|
+
import { drawArrow, drawMarker, createSmoothPath, createCatmullRomPath, applyLinePattern, applyLineTexture, getPointOnLinePath } from "./advancedLines";
|
|
3
5
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
let
|
|
6
|
+
|
|
7
|
+
export async function customLines(ctx: SKRSContext2D, options: CustomOptions[]): Promise<void> {
|
|
8
|
+
let previousEndCoordinates: { x: number; y: number } | null = null;
|
|
9
|
+
let currentStyle: CustomOptions['lineStyle'] | null = null;
|
|
7
10
|
let inSingleLineSequence = false;
|
|
8
11
|
|
|
9
12
|
for (let i = 0; i < options.length; i++) {
|
|
10
13
|
const customOption = options[i];
|
|
11
|
-
const { startCoordinates, endCoordinates, lineStyle } = customOption;
|
|
14
|
+
const { startCoordinates, endCoordinates, lineStyle, path, arrow, markers } = customOption;
|
|
12
15
|
const isSingleLine = lineStyle?.singleLine;
|
|
16
|
+
|
|
17
|
+
// Collect all points for path rendering
|
|
18
|
+
const allPoints: Array<{ x: number; y: number }> = [];
|
|
19
|
+
if (i === 0 || !isSingleLine) {
|
|
20
|
+
allPoints.push(startCoordinates);
|
|
21
|
+
}
|
|
22
|
+
allPoints.push(endCoordinates);
|
|
13
23
|
|
|
14
24
|
if (isSingleLine && !inSingleLineSequence) {
|
|
15
25
|
currentStyle = lineStyle;
|
|
@@ -20,7 +30,9 @@ export function customLines(ctx: any, options: CustomOptions[]) {
|
|
|
20
30
|
|
|
21
31
|
if (!isSingleLine && inSingleLineSequence) {
|
|
22
32
|
ctx.stroke();
|
|
23
|
-
|
|
33
|
+
if (currentStyle) {
|
|
34
|
+
applyStroke(ctx, currentStyle, startCoordinates, endCoordinates);
|
|
35
|
+
}
|
|
24
36
|
inSingleLineSequence = false;
|
|
25
37
|
currentStyle = null;
|
|
26
38
|
}
|
|
@@ -34,7 +46,30 @@ export function customLines(ctx: any, options: CustomOptions[]) {
|
|
|
34
46
|
ctx.moveTo(start.x, start.y);
|
|
35
47
|
}
|
|
36
48
|
|
|
37
|
-
|
|
49
|
+
// Apply path smoothing if specified
|
|
50
|
+
if (path && allPoints.length >= 2) {
|
|
51
|
+
ctx.beginPath();
|
|
52
|
+
if (path.type === 'smooth') {
|
|
53
|
+
createSmoothPath(ctx, allPoints, path.tension ?? 0.5, path.closed ?? false);
|
|
54
|
+
} else if (path.type === 'catmull-rom') {
|
|
55
|
+
createCatmullRomPath(ctx, allPoints, path.tension ?? 0.5, path.closed ?? false);
|
|
56
|
+
} else if (path.type === 'bezier' && allPoints.length >= 4) {
|
|
57
|
+
ctx.moveTo(allPoints[0].x, allPoints[0].y);
|
|
58
|
+
for (let j = 1; j < allPoints.length - 2; j += 3) {
|
|
59
|
+
if (j + 2 < allPoints.length) {
|
|
60
|
+
ctx.bezierCurveTo(
|
|
61
|
+
allPoints[j].x, allPoints[j].y,
|
|
62
|
+
allPoints[j + 1].x, allPoints[j + 1].y,
|
|
63
|
+
allPoints[j + 2].x, allPoints[j + 2].y
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
} else {
|
|
68
|
+
ctx.lineTo(endCoordinates.x, endCoordinates.y);
|
|
69
|
+
}
|
|
70
|
+
} else {
|
|
71
|
+
ctx.lineTo(endCoordinates.x, endCoordinates.y);
|
|
72
|
+
}
|
|
38
73
|
|
|
39
74
|
const appliedStyle = inSingleLineSequence ? currentStyle : lineStyle;
|
|
40
75
|
ctx.lineWidth = appliedStyle?.width || 1;
|
|
@@ -48,13 +83,22 @@ export function customLines(ctx: any, options: CustomOptions[]) {
|
|
|
48
83
|
ctx.lineJoin = appliedStyle?.lineJoin || 'miter';
|
|
49
84
|
ctx.lineCap = appliedStyle?.lineCap || 'butt';
|
|
50
85
|
|
|
51
|
-
|
|
86
|
+
// Apply line patterns
|
|
87
|
+
if (appliedStyle?.pattern) {
|
|
88
|
+
applyLinePattern(ctx, appliedStyle.pattern);
|
|
89
|
+
} else if (appliedStyle?.lineDash) {
|
|
52
90
|
ctx.setLineDash(appliedStyle.lineDash.dashArray || []);
|
|
53
91
|
ctx.lineDashOffset = appliedStyle.lineDash.offset || 0;
|
|
54
92
|
} else {
|
|
55
93
|
ctx.setLineDash([]);
|
|
56
94
|
}
|
|
57
95
|
|
|
96
|
+
// Apply line texture if specified
|
|
97
|
+
if (appliedStyle?.texture) {
|
|
98
|
+
await applyLineTexture(ctx, appliedStyle.texture, appliedStyle.width || 1,
|
|
99
|
+
Math.sqrt(Math.pow(endCoordinates.x - start.x, 2) + Math.pow(endCoordinates.y - start.y, 2)));
|
|
100
|
+
}
|
|
101
|
+
|
|
58
102
|
if (typeof appliedStyle?.lineRadius === 'number' && appliedStyle.lineRadius > 0) {
|
|
59
103
|
const radius = appliedStyle.lineRadius;
|
|
60
104
|
const dx = endCoordinates.x - start.x;
|
|
@@ -84,7 +128,34 @@ export function customLines(ctx: any, options: CustomOptions[]) {
|
|
|
84
128
|
|
|
85
129
|
if (!inSingleLineSequence || i === options.length - 1) {
|
|
86
130
|
ctx.stroke();
|
|
87
|
-
|
|
131
|
+
if (appliedStyle) {
|
|
132
|
+
applyStroke(ctx, appliedStyle, startCoordinates, endCoordinates);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Draw arrows if specified
|
|
137
|
+
if (arrow) {
|
|
138
|
+
const dx = endCoordinates.x - start.x;
|
|
139
|
+
const dy = endCoordinates.y - start.y;
|
|
140
|
+
const angle = Math.atan2(dy, dx);
|
|
141
|
+
const arrowColor = arrow.color || appliedStyle?.color || 'black';
|
|
142
|
+
const arrowSize = arrow.size || 10;
|
|
143
|
+
|
|
144
|
+
if (arrow.start) {
|
|
145
|
+
drawArrow(ctx, start.x, start.y, angle + Math.PI, arrowSize, arrow.style || 'filled', arrowColor);
|
|
146
|
+
}
|
|
147
|
+
if (arrow.end) {
|
|
148
|
+
drawArrow(ctx, endCoordinates.x, endCoordinates.y, angle, arrowSize, arrow.style || 'filled', arrowColor);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Draw markers if specified
|
|
153
|
+
if (markers && markers.length > 0) {
|
|
154
|
+
const linePoints = [start, endCoordinates];
|
|
155
|
+
for (const marker of markers) {
|
|
156
|
+
const point = getPointOnLinePath(linePoints, marker.position);
|
|
157
|
+
drawMarker(ctx, point.x, point.y, marker.shape, marker.size, marker.color);
|
|
158
|
+
}
|
|
88
159
|
}
|
|
89
160
|
|
|
90
161
|
previousEndCoordinates = endCoordinates;
|
|
@@ -96,7 +167,11 @@ export function customLines(ctx: any, options: CustomOptions[]) {
|
|
|
96
167
|
}
|
|
97
168
|
}
|
|
98
169
|
|
|
99
|
-
function applyStroke(ctx:
|
|
170
|
+
function applyStroke(ctx: SKRSContext2D, style: CustomOptions['lineStyle'] | undefined, start: { x: number; y: number }, end: { x: number; y: number }): void {
|
|
171
|
+
if (!style || !style.stroke) {
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
|
|
100
175
|
if (style.stroke) {
|
|
101
176
|
const { color, width, gradient, lineRadius, lineCap } = style.stroke;
|
|
102
177
|
const prevStrokeStyle = ctx.strokeStyle;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { ApexPainter } from '../../ApexPainter';
|
|
2
|
+
import { BatchOperation, ChainOperation, CanvasConfig, ImageProperties, TextProperties } from '../types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Processes multiple operations in parallel
|
|
6
|
+
* @param painter - ApexPainter instance
|
|
7
|
+
* @param operations - Array of operations to process
|
|
8
|
+
* @returns Array of result buffers
|
|
9
|
+
*/
|
|
10
|
+
export async function batchOperations(
|
|
11
|
+
painter: ApexPainter,
|
|
12
|
+
operations: BatchOperation[]
|
|
13
|
+
): Promise<Buffer[]> {
|
|
14
|
+
if (!operations || operations.length === 0) {
|
|
15
|
+
throw new Error('batch: operations array is required');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const promises = operations.map(async (op) => {
|
|
19
|
+
try {
|
|
20
|
+
switch (op.type) {
|
|
21
|
+
case 'canvas':
|
|
22
|
+
const canvasResult = await painter.createCanvas(op.config as CanvasConfig);
|
|
23
|
+
return canvasResult.buffer;
|
|
24
|
+
|
|
25
|
+
case 'image':
|
|
26
|
+
// For image operations, we need a base canvas
|
|
27
|
+
const baseCanvas = await painter.createCanvas({ width: 800, height: 600 });
|
|
28
|
+
return await painter.createImage(op.config as ImageProperties | ImageProperties[], baseCanvas);
|
|
29
|
+
|
|
30
|
+
case 'text':
|
|
31
|
+
// For text operations, we need a base canvas
|
|
32
|
+
const textBaseCanvas = await painter.createCanvas({ width: 800, height: 600 });
|
|
33
|
+
return await painter.createText(op.config as TextProperties | TextProperties[], textBaseCanvas);
|
|
34
|
+
|
|
35
|
+
case 'chart':
|
|
36
|
+
return await painter.createChart(op.config, { chartType: 'bar', chartNumber: 1 });
|
|
37
|
+
|
|
38
|
+
default:
|
|
39
|
+
throw new Error(`batch: Unknown operation type: ${op.type}`);
|
|
40
|
+
}
|
|
41
|
+
} catch (error) {
|
|
42
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
43
|
+
throw new Error(`batch: Failed to process ${op.type} operation: ${errorMessage}`);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
return Promise.all(promises);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Chains multiple operations sequentially
|
|
52
|
+
* @param painter - ApexPainter instance
|
|
53
|
+
* @param operations - Array of operations to chain
|
|
54
|
+
* @returns Final result buffer
|
|
55
|
+
*/
|
|
56
|
+
export async function chainOperations(
|
|
57
|
+
painter: ApexPainter,
|
|
58
|
+
operations: ChainOperation[]
|
|
59
|
+
): Promise<Buffer> {
|
|
60
|
+
if (!operations || operations.length === 0) {
|
|
61
|
+
throw new Error('chain: operations array is required');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
let currentBuffer: Buffer | undefined;
|
|
65
|
+
|
|
66
|
+
for (const op of operations) {
|
|
67
|
+
try {
|
|
68
|
+
const method = (painter as any)[op.method];
|
|
69
|
+
if (typeof method !== 'function') {
|
|
70
|
+
throw new Error(`chain: Method "${op.method}" does not exist on ApexPainter`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Prepare arguments - replace 'current' with current buffer
|
|
74
|
+
const args = op.args.map(arg => {
|
|
75
|
+
if (arg === 'current' || (typeof arg === 'object' && arg !== null && (arg as any).__isCurrentBuffer)) {
|
|
76
|
+
return currentBuffer;
|
|
77
|
+
}
|
|
78
|
+
return arg;
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
const result = await method.apply(painter, args);
|
|
82
|
+
|
|
83
|
+
// Update current buffer
|
|
84
|
+
if (Buffer.isBuffer(result)) {
|
|
85
|
+
currentBuffer = result;
|
|
86
|
+
} else if (result && typeof result === 'object' && 'buffer' in result) {
|
|
87
|
+
currentBuffer = (result as any).buffer;
|
|
88
|
+
} else {
|
|
89
|
+
throw new Error(`chain: Operation "${op.method}" did not return a buffer`);
|
|
90
|
+
}
|
|
91
|
+
} catch (error) {
|
|
92
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
93
|
+
throw new Error(`chain: Failed to execute "${op.method}": ${errorMessage}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!currentBuffer) {
|
|
98
|
+
throw new Error('chain: No buffer was produced from operations');
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return currentBuffer;
|
|
102
|
+
}
|
|
103
|
+
|