apexify.js 4.9.30 → 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.
Files changed (126) hide show
  1. package/README.md +56 -1
  2. package/dist/cjs/Canvas/ApexPainter.d.ts +96 -145
  3. package/dist/cjs/Canvas/ApexPainter.d.ts.map +1 -1
  4. package/dist/cjs/Canvas/ApexPainter.js +1247 -418
  5. package/dist/cjs/Canvas/ApexPainter.js.map +1 -1
  6. package/dist/cjs/Canvas/utils/Charts/charts.d.ts +7 -2
  7. package/dist/cjs/Canvas/utils/Charts/charts.d.ts.map +1 -1
  8. package/dist/cjs/Canvas/utils/Charts/charts.js +3 -1
  9. package/dist/cjs/Canvas/utils/Charts/charts.js.map +1 -1
  10. package/dist/cjs/Canvas/utils/Custom/advancedLines.d.ts +75 -0
  11. package/dist/cjs/Canvas/utils/Custom/advancedLines.d.ts.map +1 -0
  12. package/dist/cjs/Canvas/utils/Custom/advancedLines.js +263 -0
  13. package/dist/cjs/Canvas/utils/Custom/advancedLines.js.map +1 -0
  14. package/dist/cjs/Canvas/utils/Custom/customLines.d.ts +2 -1
  15. package/dist/cjs/Canvas/utils/Custom/customLines.d.ts.map +1 -1
  16. package/dist/cjs/Canvas/utils/Custom/customLines.js +73 -6
  17. package/dist/cjs/Canvas/utils/Custom/customLines.js.map +1 -1
  18. package/dist/cjs/Canvas/utils/General/batchOperations.d.ts +17 -0
  19. package/dist/cjs/Canvas/utils/General/batchOperations.d.ts.map +1 -0
  20. package/dist/cjs/Canvas/utils/General/batchOperations.js +88 -0
  21. package/dist/cjs/Canvas/utils/General/batchOperations.js.map +1 -0
  22. package/dist/cjs/Canvas/utils/General/general functions.d.ts +25 -3
  23. package/dist/cjs/Canvas/utils/General/general functions.d.ts.map +1 -1
  24. package/dist/cjs/Canvas/utils/General/general functions.js +37 -9
  25. package/dist/cjs/Canvas/utils/General/general functions.js.map +1 -1
  26. package/dist/cjs/Canvas/utils/General/imageCompression.d.ts +19 -0
  27. package/dist/cjs/Canvas/utils/General/imageCompression.d.ts.map +1 -0
  28. package/dist/cjs/Canvas/utils/General/imageCompression.js +262 -0
  29. package/dist/cjs/Canvas/utils/General/imageCompression.js.map +1 -0
  30. package/dist/cjs/Canvas/utils/General/imageStitching.d.ts +20 -0
  31. package/dist/cjs/Canvas/utils/General/imageStitching.d.ts.map +1 -0
  32. package/dist/cjs/Canvas/utils/General/imageStitching.js +227 -0
  33. package/dist/cjs/Canvas/utils/General/imageStitching.js.map +1 -0
  34. package/dist/cjs/Canvas/utils/Image/imageEffects.d.ts +37 -0
  35. package/dist/cjs/Canvas/utils/Image/imageEffects.d.ts.map +1 -0
  36. package/dist/cjs/Canvas/utils/Image/imageEffects.js +128 -0
  37. package/dist/cjs/Canvas/utils/Image/imageEffects.js.map +1 -0
  38. package/dist/cjs/Canvas/utils/Image/imageMasking.d.ts +67 -0
  39. package/dist/cjs/Canvas/utils/Image/imageMasking.d.ts.map +1 -0
  40. package/dist/cjs/Canvas/utils/Image/imageMasking.js +276 -0
  41. package/dist/cjs/Canvas/utils/Image/imageMasking.js.map +1 -0
  42. package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts.map +1 -1
  43. package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.js +16 -8
  44. package/dist/cjs/Canvas/utils/Patterns/enhancedPatternRenderer.js.map +1 -1
  45. package/dist/cjs/Canvas/utils/Texts/textPathRenderer.d.ts +17 -0
  46. package/dist/cjs/Canvas/utils/Texts/textPathRenderer.d.ts.map +1 -0
  47. package/dist/cjs/Canvas/utils/Texts/textPathRenderer.js +233 -0
  48. package/dist/cjs/Canvas/utils/Texts/textPathRenderer.js.map +1 -0
  49. package/dist/cjs/Canvas/utils/types.d.ts +121 -0
  50. package/dist/cjs/Canvas/utils/types.d.ts.map +1 -1
  51. package/dist/cjs/Canvas/utils/types.js.map +1 -1
  52. package/dist/cjs/Canvas/utils/utils.d.ts +9 -2
  53. package/dist/cjs/Canvas/utils/utils.d.ts.map +1 -1
  54. package/dist/cjs/Canvas/utils/utils.js +32 -1
  55. package/dist/cjs/Canvas/utils/utils.js.map +1 -1
  56. package/dist/cjs/tsconfig.cjs.tsbuildinfo +1 -1
  57. package/dist/esm/Canvas/ApexPainter.d.ts +96 -145
  58. package/dist/esm/Canvas/ApexPainter.d.ts.map +1 -1
  59. package/dist/esm/Canvas/ApexPainter.js +1247 -418
  60. package/dist/esm/Canvas/ApexPainter.js.map +1 -1
  61. package/dist/esm/Canvas/utils/Charts/charts.d.ts +7 -2
  62. package/dist/esm/Canvas/utils/Charts/charts.d.ts.map +1 -1
  63. package/dist/esm/Canvas/utils/Charts/charts.js +3 -1
  64. package/dist/esm/Canvas/utils/Charts/charts.js.map +1 -1
  65. package/dist/esm/Canvas/utils/Custom/advancedLines.d.ts +75 -0
  66. package/dist/esm/Canvas/utils/Custom/advancedLines.d.ts.map +1 -0
  67. package/dist/esm/Canvas/utils/Custom/advancedLines.js +263 -0
  68. package/dist/esm/Canvas/utils/Custom/advancedLines.js.map +1 -0
  69. package/dist/esm/Canvas/utils/Custom/customLines.d.ts +2 -1
  70. package/dist/esm/Canvas/utils/Custom/customLines.d.ts.map +1 -1
  71. package/dist/esm/Canvas/utils/Custom/customLines.js +73 -6
  72. package/dist/esm/Canvas/utils/Custom/customLines.js.map +1 -1
  73. package/dist/esm/Canvas/utils/General/batchOperations.d.ts +17 -0
  74. package/dist/esm/Canvas/utils/General/batchOperations.d.ts.map +1 -0
  75. package/dist/esm/Canvas/utils/General/batchOperations.js +88 -0
  76. package/dist/esm/Canvas/utils/General/batchOperations.js.map +1 -0
  77. package/dist/esm/Canvas/utils/General/general functions.d.ts +25 -3
  78. package/dist/esm/Canvas/utils/General/general functions.d.ts.map +1 -1
  79. package/dist/esm/Canvas/utils/General/general functions.js +37 -9
  80. package/dist/esm/Canvas/utils/General/general functions.js.map +1 -1
  81. package/dist/esm/Canvas/utils/General/imageCompression.d.ts +19 -0
  82. package/dist/esm/Canvas/utils/General/imageCompression.d.ts.map +1 -0
  83. package/dist/esm/Canvas/utils/General/imageCompression.js +262 -0
  84. package/dist/esm/Canvas/utils/General/imageCompression.js.map +1 -0
  85. package/dist/esm/Canvas/utils/General/imageStitching.d.ts +20 -0
  86. package/dist/esm/Canvas/utils/General/imageStitching.d.ts.map +1 -0
  87. package/dist/esm/Canvas/utils/General/imageStitching.js +227 -0
  88. package/dist/esm/Canvas/utils/General/imageStitching.js.map +1 -0
  89. package/dist/esm/Canvas/utils/Image/imageEffects.d.ts +37 -0
  90. package/dist/esm/Canvas/utils/Image/imageEffects.d.ts.map +1 -0
  91. package/dist/esm/Canvas/utils/Image/imageEffects.js +128 -0
  92. package/dist/esm/Canvas/utils/Image/imageEffects.js.map +1 -0
  93. package/dist/esm/Canvas/utils/Image/imageMasking.d.ts +67 -0
  94. package/dist/esm/Canvas/utils/Image/imageMasking.d.ts.map +1 -0
  95. package/dist/esm/Canvas/utils/Image/imageMasking.js +276 -0
  96. package/dist/esm/Canvas/utils/Image/imageMasking.js.map +1 -0
  97. package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.d.ts.map +1 -1
  98. package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.js +16 -8
  99. package/dist/esm/Canvas/utils/Patterns/enhancedPatternRenderer.js.map +1 -1
  100. package/dist/esm/Canvas/utils/Texts/textPathRenderer.d.ts +17 -0
  101. package/dist/esm/Canvas/utils/Texts/textPathRenderer.d.ts.map +1 -0
  102. package/dist/esm/Canvas/utils/Texts/textPathRenderer.js +233 -0
  103. package/dist/esm/Canvas/utils/Texts/textPathRenderer.js.map +1 -0
  104. package/dist/esm/Canvas/utils/types.d.ts +121 -0
  105. package/dist/esm/Canvas/utils/types.d.ts.map +1 -1
  106. package/dist/esm/Canvas/utils/types.js.map +1 -1
  107. package/dist/esm/Canvas/utils/utils.d.ts +9 -2
  108. package/dist/esm/Canvas/utils/utils.d.ts.map +1 -1
  109. package/dist/esm/Canvas/utils/utils.js +32 -1
  110. package/dist/esm/Canvas/utils/utils.js.map +1 -1
  111. package/dist/esm/tsconfig.esm.tsbuildinfo +1 -1
  112. package/lib/Canvas/ApexPainter.ts +1118 -266
  113. package/lib/Canvas/utils/Charts/charts.ts +16 -7
  114. package/lib/Canvas/utils/Custom/advancedLines.ts +335 -0
  115. package/lib/Canvas/utils/Custom/customLines.ts +84 -9
  116. package/lib/Canvas/utils/General/batchOperations.ts +103 -0
  117. package/lib/Canvas/utils/General/general functions.ts +85 -41
  118. package/lib/Canvas/utils/General/imageCompression.ts +316 -0
  119. package/lib/Canvas/utils/General/imageStitching.ts +252 -0
  120. package/lib/Canvas/utils/Image/imageEffects.ts +175 -0
  121. package/lib/Canvas/utils/Image/imageMasking.ts +335 -0
  122. package/lib/Canvas/utils/Patterns/enhancedPatternRenderer.ts +455 -444
  123. package/lib/Canvas/utils/Texts/textPathRenderer.ts +320 -0
  124. package/lib/Canvas/utils/types.ts +121 -0
  125. package/lib/Canvas/utils/utils.ts +49 -2
  126. 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, DataPoint } from "../types";
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(800, 600);
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
- function drawGridLines(ctx: any, leftMargin: number, topMargin: number, width: number, height: number, xLabels: any, yLabels: any, grid: any) {
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: any, data: DataItem[], canvasConfig: bgConfig, pieDataConfig: PieDataConfig) {
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: any, data: DataItem[], pieConfig: { keyBox: KeyBoxConfig; canvas: bgConfig }) {
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: DataPoint[][], lineConfig: LineChartConfig }): Promise<Buffer> {
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
- export function customLines(ctx: any, options: CustomOptions[]) {
5
- let previousEndCoordinates = null;
6
- let currentStyle = null;
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
- applyStroke(ctx, currentStyle, startCoordinates, endCoordinates);
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
- ctx.lineTo(endCoordinates.x, endCoordinates.y);
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
- if (appliedStyle?.lineDash) {
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
- applyStroke(ctx, appliedStyle, startCoordinates, endCoordinates);
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: any, style: any, start: any, end: any) {
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
+