pptx-browser 4.1.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/src/shapes.js ADDED
@@ -0,0 +1,666 @@
1
+ /**
2
+ * shapes.js — Draw OOXML preset and custom geometries onto a Canvas 2D context.
3
+ * Supports 50+ preset shapes, custom geometry paths, and parametric helpers.
4
+ */
5
+ export function drawPresetGeom(ctx, prst, x, y, w, h, adjValues) {
6
+ // Helper: angle in degrees to radians
7
+ const deg = d => d * Math.PI / 180;
8
+
9
+ // Adjustment value (0-100000 range, percentage of shape dimension)
10
+ const adj = (idx, def = 50000) => {
11
+ const v = adjValues && adjValues[idx] !== undefined ? adjValues[idx] : def;
12
+ return v / 100000;
13
+ };
14
+
15
+ const cx = x + w / 2, cy = y + h / 2;
16
+ const ss = Math.min(w, h); // short side
17
+
18
+ ctx.beginPath();
19
+
20
+ switch (prst) {
21
+ case 'rect':
22
+ case 'snip1Rect': // simplify to rect
23
+ ctx.rect(x, y, w, h);
24
+ break;
25
+
26
+ case 'roundRect': {
27
+ const r = adj(0, 16667) * ss * 0.5;
28
+ ctx.moveTo(x + r, y);
29
+ ctx.lineTo(x + w - r, y);
30
+ ctx.arcTo(x + w, y, x + w, y + r, r);
31
+ ctx.lineTo(x + w, y + h - r);
32
+ ctx.arcTo(x + w, y + h, x + w - r, y + h, r);
33
+ ctx.lineTo(x + r, y + h);
34
+ ctx.arcTo(x, y + h, x, y + h - r, r);
35
+ ctx.lineTo(x, y + r);
36
+ ctx.arcTo(x, y, x + r, y, r);
37
+ ctx.closePath();
38
+ break;
39
+ }
40
+
41
+ case 'ellipse':
42
+ case 'oval':
43
+ ctx.ellipse(cx, cy, w / 2, h / 2, 0, 0, Math.PI * 2);
44
+ break;
45
+
46
+ case 'triangle':
47
+ case 'isoscelesTriangle':
48
+ ctx.moveTo(cx, y);
49
+ ctx.lineTo(x + w, y + h);
50
+ ctx.lineTo(x, y + h);
51
+ ctx.closePath();
52
+ break;
53
+
54
+ case 'rightTriangle':
55
+ ctx.moveTo(x, y);
56
+ ctx.lineTo(x + w, y + h);
57
+ ctx.lineTo(x, y + h);
58
+ ctx.closePath();
59
+ break;
60
+
61
+ case 'parallelogram': {
62
+ const a1 = adj(0, 25000);
63
+ const off = w * a1;
64
+ ctx.moveTo(x + off, y);
65
+ ctx.lineTo(x + w, y);
66
+ ctx.lineTo(x + w - off, y + h);
67
+ ctx.lineTo(x, y + h);
68
+ ctx.closePath();
69
+ break;
70
+ }
71
+
72
+ case 'trapezoid': {
73
+ const a1 = adj(0, 25000);
74
+ const off = w * a1;
75
+ ctx.moveTo(x + off, y);
76
+ ctx.lineTo(x + w - off, y);
77
+ ctx.lineTo(x + w, y + h);
78
+ ctx.lineTo(x, y + h);
79
+ ctx.closePath();
80
+ break;
81
+ }
82
+
83
+ case 'diamond':
84
+ ctx.moveTo(cx, y);
85
+ ctx.lineTo(x + w, cy);
86
+ ctx.lineTo(cx, y + h);
87
+ ctx.lineTo(x, cy);
88
+ ctx.closePath();
89
+ break;
90
+
91
+ case 'pentagon': drawRegularPolygon(ctx, cx, cy, w/2, h/2, 5, -Math.PI/2); break;
92
+ case 'hexagon': drawRegularPolygon(ctx, cx, cy, w/2, h/2, 6, 0); break;
93
+ case 'heptagon': drawRegularPolygon(ctx, cx, cy, w/2, h/2, 7, -Math.PI/2); break;
94
+ case 'octagon': drawRegularPolygon(ctx, cx, cy, w/2, h/2, 8, Math.PI/8); break;
95
+ case 'decagon': drawRegularPolygon(ctx, cx, cy, w/2, h/2, 10, -Math.PI/2); break;
96
+ case 'dodecagon': drawRegularPolygon(ctx, cx, cy, w/2, h/2, 12, 0); break;
97
+
98
+ case 'plus':
99
+ case 'cross': {
100
+ const a1 = adj(0, 25000);
101
+ const t = ss * a1;
102
+ const bx = cx - t/2, tx = cx + t/2;
103
+ const by = cy - t/2, ty = cy + t/2;
104
+ ctx.moveTo(bx, y); ctx.lineTo(tx, y);
105
+ ctx.lineTo(tx, by); ctx.lineTo(x+w, by);
106
+ ctx.lineTo(x+w, ty); ctx.lineTo(tx, ty);
107
+ ctx.lineTo(tx, y+h); ctx.lineTo(bx, y+h);
108
+ ctx.lineTo(bx, ty); ctx.lineTo(x, ty);
109
+ ctx.lineTo(x, by); ctx.lineTo(bx, by);
110
+ ctx.closePath();
111
+ break;
112
+ }
113
+
114
+ case 'star4':
115
+ drawStar(ctx, cx, cy, w/2, h/2, 4, adj(0, 37500)); break;
116
+ case 'star5':
117
+ drawStar(ctx, cx, cy, w/2, h/2, 5, adj(0, 19098), -Math.PI/2); break;
118
+ case 'star6':
119
+ drawStar(ctx, cx, cy, w/2, h/2, 6, adj(0, 28868), 0); break;
120
+ case 'star7':
121
+ drawStar(ctx, cx, cy, w/2, h/2, 7, adj(0, 34601), -Math.PI/2); break;
122
+ case 'star8':
123
+ drawStar(ctx, cx, cy, w/2, h/2, 8, adj(0, 29289), Math.PI/8); break;
124
+ case 'star10':
125
+ drawStar(ctx, cx, cy, w/2, h/2, 10, adj(0, 30902), -Math.PI/2); break;
126
+ case 'star12':
127
+ drawStar(ctx, cx, cy, w/2, h/2, 12, adj(0, 37720), 0); break;
128
+ case 'star16':
129
+ drawStar(ctx, cx, cy, w/2, h/2, 16, adj(0, 37500), 0); break;
130
+ case 'star24':
131
+ drawStar(ctx, cx, cy, w/2, h/2, 24, adj(0, 37500), 0); break;
132
+ case 'star32':
133
+ drawStar(ctx, cx, cy, w/2, h/2, 32, adj(0, 37500), 0); break;
134
+
135
+ case 'rightArrow': {
136
+ const ah = adj(0, 50000); // arrow head width ratio
137
+ const aw = adj(1, 50000); // arrow head height ratio
138
+ const bh = (h - h * aw) / 2;
139
+ const nb = (h * aw - h) / 2;
140
+ const ax = x + w * (1 - ah);
141
+ ctx.moveTo(x, y + bh);
142
+ ctx.lineTo(ax, y + bh);
143
+ ctx.lineTo(ax, y);
144
+ ctx.lineTo(x + w, cy);
145
+ ctx.lineTo(ax, y + h);
146
+ ctx.lineTo(ax, y + h - bh);
147
+ ctx.lineTo(x, y + h - bh);
148
+ ctx.closePath();
149
+ break;
150
+ }
151
+
152
+ case 'leftArrow': {
153
+ const ah = adj(0, 50000);
154
+ const aw = adj(1, 50000);
155
+ const bh = (h - h * aw) / 2;
156
+ const ax = x + w * ah;
157
+ ctx.moveTo(x + w, y + bh);
158
+ ctx.lineTo(ax, y + bh);
159
+ ctx.lineTo(ax, y);
160
+ ctx.lineTo(x, cy);
161
+ ctx.lineTo(ax, y + h);
162
+ ctx.lineTo(ax, y + h - bh);
163
+ ctx.lineTo(x + w, y + h - bh);
164
+ ctx.closePath();
165
+ break;
166
+ }
167
+
168
+ case 'upArrow': {
169
+ const ah = adj(0, 50000);
170
+ const aw = adj(1, 50000);
171
+ const bw = (w - w * aw) / 2;
172
+ const ay = y + h * ah;
173
+ ctx.moveTo(x + bw, y + h);
174
+ ctx.lineTo(x + bw, ay);
175
+ ctx.lineTo(x, ay);
176
+ ctx.lineTo(cx, y);
177
+ ctx.lineTo(x + w, ay);
178
+ ctx.lineTo(x + w - bw, ay);
179
+ ctx.lineTo(x + w - bw, y + h);
180
+ ctx.closePath();
181
+ break;
182
+ }
183
+
184
+ case 'downArrow': {
185
+ const ah = adj(0, 50000);
186
+ const aw = adj(1, 50000);
187
+ const bw = (w - w * aw) / 2;
188
+ const ay = y + h * (1 - ah);
189
+ ctx.moveTo(x + bw, y);
190
+ ctx.lineTo(x + bw, ay);
191
+ ctx.lineTo(x, ay);
192
+ ctx.lineTo(cx, y + h);
193
+ ctx.lineTo(x + w, ay);
194
+ ctx.lineTo(x + w - bw, ay);
195
+ ctx.lineTo(x + w - bw, y);
196
+ ctx.closePath();
197
+ break;
198
+ }
199
+
200
+ case 'leftRightArrow': {
201
+ const ah = adj(0, 25000);
202
+ const aw = adj(1, 50000);
203
+ const bh = (h - h * aw) / 2;
204
+ const lax = x + w * ah;
205
+ const rax = x + w * (1 - ah);
206
+ ctx.moveTo(x, cy);
207
+ ctx.lineTo(lax, y);
208
+ ctx.lineTo(lax, y + bh);
209
+ ctx.lineTo(rax, y + bh);
210
+ ctx.lineTo(rax, y);
211
+ ctx.lineTo(x + w, cy);
212
+ ctx.lineTo(rax, y + h);
213
+ ctx.lineTo(rax, y + h - bh);
214
+ ctx.lineTo(lax, y + h - bh);
215
+ ctx.lineTo(lax, y + h);
216
+ ctx.closePath();
217
+ break;
218
+ }
219
+
220
+ case 'upDownArrow': {
221
+ const ah = adj(0, 25000);
222
+ const aw = adj(1, 50000);
223
+ const bw = (w - w * aw) / 2;
224
+ const tay = y + h * ah;
225
+ const bay = y + h * (1 - ah);
226
+ ctx.moveTo(cx, y);
227
+ ctx.lineTo(x + w, tay);
228
+ ctx.lineTo(x + w - bw, tay);
229
+ ctx.lineTo(x + w - bw, bay);
230
+ ctx.lineTo(x + w, bay);
231
+ ctx.lineTo(cx, y + h);
232
+ ctx.lineTo(x, bay);
233
+ ctx.lineTo(x + bw, bay);
234
+ ctx.lineTo(x + bw, tay);
235
+ ctx.lineTo(x, tay);
236
+ ctx.closePath();
237
+ break;
238
+ }
239
+
240
+ case 'chevron': {
241
+ const a = adj(0, 50000);
242
+ const off = w * a;
243
+ ctx.moveTo(x, y);
244
+ ctx.lineTo(x + w - off, y);
245
+ ctx.lineTo(x + w, cy);
246
+ ctx.lineTo(x + w - off, y + h);
247
+ ctx.lineTo(x, y + h);
248
+ ctx.lineTo(x + off, cy);
249
+ ctx.closePath();
250
+ break;
251
+ }
252
+
253
+ case 'pentagon5': // not standard but some files use it
254
+ case 'homePlate': {
255
+ const a = adj(0, 50000);
256
+ ctx.moveTo(x, y);
257
+ ctx.lineTo(x + w * (1 - a), y);
258
+ ctx.lineTo(x + w, cy);
259
+ ctx.lineTo(x + w * (1 - a), y + h);
260
+ ctx.lineTo(x, y + h);
261
+ ctx.closePath();
262
+ break;
263
+ }
264
+
265
+ case 'arc': {
266
+ const stAng = adj(0, 16200000 / 60000) * Math.PI / 180; // stored as 60000ths of degree
267
+ const swAng = adj(1, 28800000 / 60000) * Math.PI / 180;
268
+ ctx.moveTo(cx, cy);
269
+ ctx.arc(cx, cy, Math.min(w, h) / 2, stAng, stAng + swAng);
270
+ ctx.closePath();
271
+ break;
272
+ }
273
+
274
+ case 'blockArc': {
275
+ // outer arc minus inner arc
276
+ const stAng = (adj(0, 0) * 360 - 90) * Math.PI / 180;
277
+ const swAng = adj(1, 25000) * 360 * Math.PI / 180;
278
+ const tck = adj(2, 25000) * Math.min(w, h) / 2;
279
+ const outerR = Math.min(w, h) / 2;
280
+ const innerR = outerR - tck;
281
+ ctx.moveTo(cx + outerR * Math.cos(stAng), cy + outerR * Math.sin(stAng));
282
+ ctx.arc(cx, cy, outerR, stAng, stAng + swAng);
283
+ ctx.arc(cx, cy, Math.max(1, innerR), stAng + swAng, stAng, true);
284
+ ctx.closePath();
285
+ break;
286
+ }
287
+
288
+ case 'line':
289
+ case 'straightConnector1':
290
+ ctx.moveTo(x, cy);
291
+ ctx.lineTo(x + w, cy);
292
+ break;
293
+
294
+ case 'bentConnector3':
295
+ case 'bentConnector4':
296
+ case 'bentConnector5':
297
+ case 'elbowConnector':
298
+ ctx.moveTo(x, y);
299
+ ctx.lineTo(x, cy);
300
+ ctx.lineTo(x + w, cy);
301
+ ctx.lineTo(x + w, y + h);
302
+ break;
303
+
304
+ case 'curvedConnector3':
305
+ case 'curvedConnector4':
306
+ ctx.moveTo(x, y);
307
+ ctx.bezierCurveTo(x, y + h/2, x + w, y + h/2, x + w, y + h);
308
+ break;
309
+
310
+ case 'heart': {
311
+ drawHeart(ctx, x, y, w, h);
312
+ break;
313
+ }
314
+
315
+ case 'lightningBolt': {
316
+ ctx.moveTo(x + w * 0.6, y);
317
+ ctx.lineTo(x + w * 0.2, y + h * 0.45);
318
+ ctx.lineTo(x + w * 0.5, y + h * 0.45);
319
+ ctx.lineTo(x + w * 0.4, y + h);
320
+ ctx.lineTo(x + w * 0.8, y + h * 0.55);
321
+ ctx.lineTo(x + w * 0.5, y + h * 0.55);
322
+ ctx.closePath();
323
+ break;
324
+ }
325
+
326
+ case 'moon': {
327
+ ctx.arc(cx + w * 0.1, cy, h * 0.5, deg(-120), deg(120));
328
+ ctx.arc(cx + w * 0.4, cy, h * 0.45, deg(120), deg(-120), true);
329
+ ctx.closePath();
330
+ break;
331
+ }
332
+
333
+ case 'noSmoking': {
334
+ const r = Math.min(w, h) / 2;
335
+ ctx.arc(cx, cy, r, 0, Math.PI * 2);
336
+ ctx.moveTo(cx - r * 0.7, cy + r * 0.7);
337
+ ctx.lineTo(cx + r * 0.7, cy - r * 0.7);
338
+ break;
339
+ }
340
+
341
+ case 'flowChartProcess':
342
+ ctx.rect(x, y, w, h);
343
+ break;
344
+
345
+ case 'flowChartDecision':
346
+ ctx.moveTo(cx, y);
347
+ ctx.lineTo(x + w, cy);
348
+ ctx.lineTo(cx, y + h);
349
+ ctx.lineTo(x, cy);
350
+ ctx.closePath();
351
+ break;
352
+
353
+ case 'flowChartTerminator': {
354
+ const r2 = h / 2;
355
+ ctx.moveTo(x + r2, y);
356
+ ctx.lineTo(x + w - r2, y);
357
+ ctx.arc(x + w - r2, cy, r2, -Math.PI/2, Math.PI/2);
358
+ ctx.lineTo(x + r2, y + h);
359
+ ctx.arc(x + r2, cy, r2, Math.PI/2, -Math.PI/2);
360
+ ctx.closePath();
361
+ break;
362
+ }
363
+
364
+ case 'flowChartDocument': {
365
+ ctx.moveTo(x, y);
366
+ ctx.lineTo(x + w, y);
367
+ ctx.lineTo(x + w, y + h * 0.8);
368
+ ctx.bezierCurveTo(x + w * 0.75, y + h * 0.8, x + w * 0.75, y + h, x + w * 0.5, y + h);
369
+ ctx.bezierCurveTo(x + w * 0.25, y + h, x + w * 0.25, y + h * 0.8, x, y + h * 0.8);
370
+ ctx.closePath();
371
+ break;
372
+ }
373
+
374
+ case 'flowChartDatabase':
375
+ case 'cylinder': {
376
+ const ry = h * 0.1;
377
+ ctx.moveTo(x, y + ry);
378
+ ctx.ellipse(cx, y + ry, w/2, ry, 0, Math.PI, 0);
379
+ ctx.lineTo(x + w, y + h - ry);
380
+ ctx.ellipse(cx, y + h - ry, w/2, ry, 0, 0, Math.PI);
381
+ ctx.closePath();
382
+ break;
383
+ }
384
+
385
+ case 'cube': {
386
+ const d = w * 0.15;
387
+ ctx.moveTo(x + d, y);
388
+ ctx.lineTo(x + w, y);
389
+ ctx.lineTo(x + w, y + h - d);
390
+ ctx.lineTo(x + w - d, y + h);
391
+ ctx.lineTo(x, y + h);
392
+ ctx.lineTo(x, y + d);
393
+ ctx.closePath();
394
+ ctx.moveTo(x + d, y);
395
+ ctx.lineTo(x + d, y + d);
396
+ ctx.lineTo(x, y + d);
397
+ ctx.moveTo(x + d, y + d);
398
+ ctx.lineTo(x + w, y + d);
399
+ break;
400
+ }
401
+
402
+ case 'callout1':
403
+ case 'borderCallout1':
404
+ case 'wedgeRectCallout': {
405
+ ctx.rect(x, y, w, h * 0.8);
406
+ ctx.moveTo(cx - w * 0.1, y + h * 0.8);
407
+ ctx.lineTo(cx, y + h);
408
+ ctx.lineTo(cx + w * 0.1, y + h * 0.8);
409
+ ctx.closePath();
410
+ break;
411
+ }
412
+
413
+ case 'wedgeRoundRectCallout': {
414
+ const r3 = h * 0.05;
415
+ // Round rect
416
+ ctx.moveTo(x + r3, y);
417
+ ctx.lineTo(x + w - r3, y);
418
+ ctx.arcTo(x + w, y, x + w, y + r3, r3);
419
+ ctx.lineTo(x + w, y + h * 0.75 - r3);
420
+ ctx.arcTo(x + w, y + h * 0.75, x + w - r3, y + h * 0.75, r3);
421
+ ctx.lineTo(cx + w * 0.1, y + h * 0.75);
422
+ ctx.lineTo(cx, y + h);
423
+ ctx.lineTo(cx - w * 0.1, y + h * 0.75);
424
+ ctx.lineTo(x + r3, y + h * 0.75);
425
+ ctx.arcTo(x, y + h * 0.75, x, y + h * 0.75 - r3, r3);
426
+ ctx.lineTo(x, y + r3);
427
+ ctx.arcTo(x, y, x + r3, y, r3);
428
+ ctx.closePath();
429
+ break;
430
+ }
431
+
432
+ case 'wedgeEllipseCallout': {
433
+ ctx.ellipse(cx, cy - h * 0.05, w/2, h * 0.45, 0, 0, Math.PI * 2);
434
+ ctx.moveTo(cx - w * 0.1, cy + h * 0.4);
435
+ ctx.lineTo(cx, y + h);
436
+ ctx.lineTo(cx + w * 0.1, cy + h * 0.4);
437
+ ctx.closePath();
438
+ break;
439
+ }
440
+
441
+ case 'cloudCallout':
442
+ case 'cloud': {
443
+ // Cloud: overlapping circles for bumps, bottom arc for base
444
+ // Use an offscreen clip region via compositing would be ideal;
445
+ // here we approximate with a path of arcs.
446
+ ctx.beginPath();
447
+ const cBumps = [
448
+ { cx: cx - w * 0.22, cy: cy - h * 0.12, r: w * 0.16 },
449
+ { cx: cx - w * 0.07, cy: cy - h * 0.22, r: w * 0.18 },
450
+ { cx: cx + w * 0.10, cy: cy - h * 0.22, r: w * 0.17 },
451
+ { cx: cx + w * 0.25, cy: cy - h * 0.12, r: w * 0.15 },
452
+ { cx: cx + w * 0.35, cy: cy + h * 0.05, r: w * 0.13 },
453
+ { cx: cx - w * 0.30, cy: cy + h * 0.05, r: w * 0.13 },
454
+ ];
455
+ for (const b of cBumps) ctx.arc(b.cx, b.cy, b.r, 0, Math.PI * 2);
456
+ // Base rectangle joining the bottoms of side bumps
457
+ ctx.rect(cx - w * 0.35, cy, w * 0.70, h * 0.25);
458
+ break;
459
+ }
460
+
461
+ case 'smileyFace': {
462
+ ctx.arc(cx, cy, Math.min(w,h)/2, 0, Math.PI * 2);
463
+ // Draw smile as open path - eye dots drawn with filled circles in renderSp
464
+ break;
465
+ }
466
+
467
+ case 'donut': {
468
+ const r4 = Math.min(w,h)/2;
469
+ const ir = r4 * adj(0, 25000);
470
+ ctx.arc(cx, cy, r4, 0, Math.PI * 2);
471
+ ctx.arc(cx, cy, ir, Math.PI * 2, 0, true);
472
+ break;
473
+ }
474
+
475
+ case 'bracketPair': {
476
+ const r5 = w * 0.2;
477
+ ctx.moveTo(x + r5, y);
478
+ ctx.arcTo(x, y, x, y + r5, r5);
479
+ ctx.lineTo(x, y + h - r5);
480
+ ctx.arcTo(x, y + h, x + r5, y + h, r5);
481
+ ctx.moveTo(x + w - r5, y);
482
+ ctx.arcTo(x + w, y, x + w, y + r5, r5);
483
+ ctx.lineTo(x + w, y + h - r5);
484
+ ctx.arcTo(x + w, y + h, x + w - r5, y + h, r5);
485
+ break;
486
+ }
487
+
488
+ case 'bracePair': {
489
+ const r6 = h * 0.15;
490
+ // Left brace
491
+ ctx.moveTo(cx - w*0.35, y);
492
+ ctx.bezierCurveTo(cx - w*0.45, y, cx - w*0.45, y, cx - w*0.45, y + r6);
493
+ ctx.lineTo(cx - w*0.45, cy - r6);
494
+ ctx.bezierCurveTo(cx - w*0.45, cy, cx - w*0.5, cy, cx - w*0.5, cy);
495
+ ctx.bezierCurveTo(cx - w*0.5, cy, cx - w*0.45, cy, cx - w*0.45, cy + r6);
496
+ ctx.lineTo(cx - w*0.45, y + h - r6);
497
+ ctx.bezierCurveTo(cx - w*0.45, y + h, cx - w*0.35, y + h, cx - w*0.35, y + h);
498
+ break;
499
+ }
500
+
501
+ case 'irregularSeal1':
502
+ case 'irregularSeal2': {
503
+ // Star-like jagged shape
504
+ drawStar(ctx, cx, cy, w/2, h/2, 12, adj(0, 42533), 0);
505
+ break;
506
+ }
507
+
508
+ case 'accentCallout1':
509
+ case 'accentCallout2':
510
+ case 'calloutWedgeRect':
511
+ ctx.rect(x, y, w, h);
512
+ break;
513
+
514
+ case 'flowChartAlternateProcess': {
515
+ const r7 = h * 0.15;
516
+ ctx.moveTo(x + r7, y);
517
+ ctx.lineTo(x + w - r7, y);
518
+ ctx.arcTo(x + w, y, x + w, y + r7, r7);
519
+ ctx.lineTo(x + w, y + h - r7);
520
+ ctx.arcTo(x + w, y + h, x + w - r7, y + h, r7);
521
+ ctx.lineTo(x + r7, y + h);
522
+ ctx.arcTo(x, y + h, x, y + h - r7, r7);
523
+ ctx.lineTo(x, y + r7);
524
+ ctx.arcTo(x, y, x + r7, y, r7);
525
+ ctx.closePath();
526
+ break;
527
+ }
528
+
529
+ case 'flowChartConnector':
530
+ ctx.arc(cx, cy, Math.min(w,h)/2, 0, Math.PI*2);
531
+ break;
532
+
533
+ case 'flowChartInputOutput': {
534
+ const off2 = w * 0.2;
535
+ ctx.moveTo(x + off2, y);
536
+ ctx.lineTo(x + w, y);
537
+ ctx.lineTo(x + w - off2, y + h);
538
+ ctx.lineTo(x, y + h);
539
+ ctx.closePath();
540
+ break;
541
+ }
542
+
543
+ case 'flowChartPredefinedProcess': {
544
+ ctx.rect(x, y, w, h);
545
+ ctx.moveTo(x + w*0.1, y);
546
+ ctx.lineTo(x + w*0.1, y + h);
547
+ ctx.moveTo(x + w*0.9, y);
548
+ ctx.lineTo(x + w*0.9, y + h);
549
+ break;
550
+ }
551
+
552
+ case 'flowChartManualInput': {
553
+ ctx.moveTo(x, y + h * 0.2);
554
+ ctx.lineTo(x + w, y);
555
+ ctx.lineTo(x + w, y + h);
556
+ ctx.lineTo(x, y + h);
557
+ ctx.closePath();
558
+ break;
559
+ }
560
+
561
+ case 'flowChartPreparation': {
562
+ const off3 = w * 0.2;
563
+ ctx.moveTo(x + off3, y);
564
+ ctx.lineTo(x + w - off3, y);
565
+ ctx.lineTo(x + w, cy);
566
+ ctx.lineTo(x + w - off3, y + h);
567
+ ctx.lineTo(x + off3, y + h);
568
+ ctx.lineTo(x, cy);
569
+ ctx.closePath();
570
+ break;
571
+ }
572
+
573
+ case 'ribbon':
574
+ case 'ribbon2': {
575
+ const notchH = h * 0.3;
576
+ ctx.moveTo(x, y + notchH/2);
577
+ ctx.lineTo(x + w*0.1, y);
578
+ ctx.lineTo(x + w*0.1, y + h - notchH);
579
+ ctx.lineTo(x, y + h);
580
+ ctx.lineTo(x + w*0.5, y + h - notchH);
581
+ ctx.lineTo(x + w, y + h);
582
+ ctx.lineTo(x + w - w*0.1, y + h - notchH);
583
+ ctx.lineTo(x + w - w*0.1, y);
584
+ ctx.lineTo(x + w, y + notchH/2);
585
+ ctx.lineTo(x + w*0.5, y + notchH);
586
+ ctx.closePath();
587
+ break;
588
+ }
589
+
590
+ case 'ellipseRibbon':
591
+ case 'ellipseRibbon2': {
592
+ ctx.ellipse(cx, cy, w/2, h/2, 0, 0, Math.PI * 2);
593
+ break;
594
+ }
595
+
596
+ case 'teardrop': {
597
+ const tr = Math.min(w, h) * 0.45;
598
+ ctx.arc(cx, cy + tr, tr, -Math.PI, 0);
599
+ ctx.bezierCurveTo(cx + tr, cy + tr - tr*0.55, cx + tr*0.1, cy - h*0.4, cx, y);
600
+ ctx.bezierCurveTo(cx - tr*0.1, cy - h*0.4, cx - tr, cy + tr - tr*0.55, cx - tr, cy + tr);
601
+ ctx.closePath();
602
+ break;
603
+ }
604
+
605
+ default:
606
+ // Unknown shape — draw rectangle
607
+ ctx.rect(x, y, w, h);
608
+ return false;
609
+ }
610
+ return true;
611
+ }
612
+
613
+ export function drawRegularPolygon(ctx, cx, cy, rx, ry, n, startAngle = 0) {
614
+ for (let i = 0; i < n; i++) {
615
+ const angle = startAngle + (i / n) * Math.PI * 2;
616
+ const px = cx + rx * Math.cos(angle);
617
+ const py = cy + ry * Math.sin(angle);
618
+ if (i === 0) ctx.moveTo(px, py);
619
+ else ctx.lineTo(px, py);
620
+ }
621
+ ctx.closePath();
622
+ }
623
+
624
+ export function drawStar(ctx, cx, cy, rx, ry, points, innerRatio = 0.5, startAngle = -Math.PI/2) {
625
+ for (let i = 0; i < points * 2; i++) {
626
+ const angle = startAngle + (i / (points * 2)) * Math.PI * 2;
627
+ const isInner = i % 2 === 1;
628
+ const r_x = isInner ? rx * innerRatio : rx;
629
+ const r_y = isInner ? ry * innerRatio : ry;
630
+ const px = cx + r_x * Math.cos(angle);
631
+ const py = cy + r_y * Math.sin(angle);
632
+ if (i === 0) ctx.moveTo(px, py);
633
+ else ctx.lineTo(px, py);
634
+ }
635
+ ctx.closePath();
636
+ }
637
+
638
+ export function drawHeart(ctx, x, y, w, h) {
639
+ ctx.beginPath();
640
+ const tx = x + w / 2;
641
+ const topY = y + h * 0.25;
642
+ ctx.moveTo(tx, y + h * 0.9);
643
+ // Left side
644
+ ctx.bezierCurveTo(
645
+ x - w * 0.1, y + h * 0.6,
646
+ x - w * 0.1, topY - h * 0.05,
647
+ tx - w * 0.25, topY - h * 0.1
648
+ );
649
+ ctx.bezierCurveTo(
650
+ tx - w * 0.5 + w * 0.05, y + h * 0.05,
651
+ tx - w * 0.03, y + h * 0.05,
652
+ tx, topY - h * 0.15
653
+ );
654
+ // Right side
655
+ ctx.bezierCurveTo(
656
+ tx + w * 0.03, y + h * 0.05,
657
+ tx + w * 0.5 - w * 0.05, y + h * 0.05,
658
+ tx + w * 0.25, topY - h * 0.1
659
+ );
660
+ ctx.bezierCurveTo(
661
+ x + w * 1.1, topY - h * 0.05,
662
+ x + w * 1.1, y + h * 0.6,
663
+ tx, y + h * 0.9
664
+ );
665
+ ctx.closePath();
666
+ }