forked from evanw/figma-fill-rule-editor
-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathui.html
807 lines (688 loc) · 39.1 KB
/
ui.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
<style>
@import url('https://rsms.me/inter/inter.css');
body {
user-select: none;
cursor: default;
font-family: 'Inter', sans-serif;
margin: 0;
left: 0;
top: 0;
right: 0;
bottom: 0;
font-size: 11px;
}
canvas {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
}
#instructions {
display: none;
padding: 12px;
}
#instructions>p:last-child {
margin-top: 0;
}
.illustration {
display: flex;
justify-content: center;
}
ol {
padding-left: 12px;
/* Reduces space before numbers. Adjust as needed */
}
ul {
padding-left: 12px;
/* Reduces space before numbers. Adjust as needed */
}
.toStart {
padding-top: 12px;
padding-bottom: 24px;
text-align: center;
animation: blink 2s infinite;
font-weight: 400;
}
@keyframes blink {
0% {
opacity: 1;
}
50% {
opacity: 0.5;
}
100% {
opacity: 1;
}
}
</style>
<canvas></canvas>
<div id="instructions">
<h2 class="toStart">Select a vector object to start</h2>
<p>Take full control of your vector object paths with this comprehensive plugin. Edit fill rules, set first nodes, and
preview changes in real-time.</p>
<h3>Features:</h3>
<ul>
<li>Edit fill rules and preview of changes (non-zero vs. even-odd)</li>
<li>Reverse path orientations</li>
<li>Set the first node for each path</li>
</ul>
<div class="illustration">
<svg width="255" height="130" viewBox="0 0 255 130" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="254.212" height="129.451" fill="white" />
<path fill-rule="nonzero" clip-rule="nonzero"
d="M55.553 8L26.1636 98.451L103.106 42.549H8L84.942 98.451L55.553 8Z" fill="#3F9FFF" fill-opacity="0.25" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M55.553 8L26.1636 98.451L103.106 42.549H8L84.942 98.451L55.553 8Z" stroke="black" />
<path
d="M24.0303 109.724V118.451H23.0075L18.2518 111.599H18.1666V118.451H17.1098V109.724H18.1325L22.9053 116.593H22.9905V109.724H24.0303ZM28.6677 118.587C28.0768 118.587 27.5583 118.447 27.1123 118.165C26.6691 117.884 26.3225 117.491 26.0725 116.985C25.8254 116.479 25.7018 115.888 25.7018 115.212C25.7018 114.531 25.8254 113.935 26.0725 113.427C26.3225 112.918 26.6691 112.523 27.1123 112.242C27.5583 111.961 28.0768 111.82 28.6677 111.82C29.2586 111.82 29.7756 111.961 30.2188 112.242C30.6648 112.523 31.0114 112.918 31.2586 113.427C31.5086 113.935 31.6336 114.531 31.6336 115.212C31.6336 115.888 31.5086 116.479 31.2586 116.985C31.0114 117.491 30.6648 117.884 30.2188 118.165C29.7756 118.447 29.2586 118.587 28.6677 118.587ZM28.6677 117.684C29.1165 117.684 29.4859 117.569 29.7756 117.339C30.0654 117.109 30.2799 116.806 30.4191 116.431C30.5583 116.056 30.6279 115.65 30.6279 115.212C30.6279 114.775 30.5583 114.367 30.4191 113.989C30.2799 113.612 30.0654 113.306 29.7756 113.073C29.4859 112.84 29.1165 112.724 28.6677 112.724C28.2188 112.724 27.8495 112.84 27.5597 113.073C27.27 113.306 27.0555 113.612 26.9163 113.989C26.7771 114.367 26.7075 114.775 26.7075 115.212C26.7075 115.65 26.7771 116.056 26.9163 116.431C27.0555 116.806 27.27 117.109 27.5597 117.339C27.8495 117.569 28.2188 117.684 28.6677 117.684ZM34.1744 114.513V118.451H33.1687V111.906H34.1403V112.928H34.2256C34.379 112.596 34.6119 112.329 34.9244 112.127C35.2369 111.923 35.6403 111.82 36.1347 111.82C36.5778 111.82 36.9656 111.911 37.298 112.093C37.6304 112.272 37.8889 112.545 38.0736 112.911C38.2582 113.275 38.3506 113.735 38.3506 114.292V118.451H37.3449V114.36C37.3449 113.846 37.2114 113.445 36.9443 113.158C36.6773 112.869 36.3108 112.724 35.8449 112.724C35.5239 112.724 35.2369 112.793 34.9841 112.933C34.7341 113.072 34.5366 113.275 34.3918 113.542C34.2469 113.809 34.1744 114.133 34.1744 114.513ZM43.9383 114.241V115.178H40.1201V114.241H43.9383ZM45.5203 118.451V117.684L49.2362 112.911V112.843H45.6396V111.906H50.5317V112.707L46.918 117.445V117.513H50.651V118.451H45.5203ZM54.7568 118.587C54.1261 118.587 53.5821 118.448 53.1247 118.17C52.6702 117.888 52.3193 117.496 52.0722 116.994C51.8278 116.488 51.7057 115.9 51.7057 115.229C51.7057 114.559 51.8278 113.968 52.0722 113.457C52.3193 112.942 52.6631 112.542 53.1034 112.255C53.5466 111.965 54.0636 111.82 54.6545 111.82C54.9955 111.82 55.3321 111.877 55.6645 111.991C55.9969 112.104 56.2994 112.289 56.5722 112.545C56.8449 112.798 57.0622 113.133 57.2241 113.55C57.3861 113.968 57.467 114.482 57.467 115.093V115.519H52.4216V114.65H56.4443C56.4443 114.281 56.3705 113.951 56.2227 113.661C56.0778 113.371 55.8705 113.143 55.6006 112.975C55.3335 112.808 55.0182 112.724 54.6545 112.724C54.254 112.724 53.9074 112.823 53.6148 113.022C53.325 113.218 53.102 113.474 52.9457 113.789C52.7895 114.104 52.7114 114.442 52.7114 114.803V115.383C52.7114 115.877 52.7966 116.296 52.967 116.64C53.1403 116.981 53.3804 117.241 53.6872 117.42C53.994 117.596 54.3506 117.684 54.7568 117.684C55.021 117.684 55.2597 117.647 55.4727 117.573C55.6886 117.496 55.8747 117.383 56.031 117.232C56.1872 117.079 56.308 116.888 56.3932 116.661L57.3648 116.934C57.2625 117.263 57.0906 117.553 56.8491 117.803C56.6077 118.05 56.3094 118.244 55.9543 118.383C55.5991 118.519 55.2 118.587 54.7568 118.587ZM58.9969 118.451V111.906H59.9685V112.894H60.0366C60.156 112.57 60.3719 112.308 60.6844 112.106C60.9969 111.904 61.3491 111.803 61.7412 111.803C61.8151 111.803 61.9074 111.805 62.0182 111.808C62.129 111.81 62.2128 111.815 62.2696 111.82V112.843C62.2355 112.835 62.1574 112.822 62.0352 112.805C61.9159 112.785 61.7895 112.775 61.656 112.775C61.3378 112.775 61.0537 112.842 60.8037 112.975C60.5565 113.106 60.3605 113.288 60.2156 113.521C60.0736 113.751 60.0026 114.013 60.0026 114.309V118.451H58.9969ZM65.9216 118.587C65.3307 118.587 64.8122 118.447 64.3662 118.165C63.923 117.884 63.5764 117.491 63.3264 116.985C63.0793 116.479 62.9557 115.888 62.9557 115.212C62.9557 114.531 63.0793 113.935 63.3264 113.427C63.5764 112.918 63.923 112.523 64.3662 112.242C64.8122 111.961 65.3307 111.82 65.9216 111.82C66.5125 111.82 67.0295 111.961 67.4727 112.242C67.9187 112.523 68.2653 112.918 68.5125 113.427C68.7625 113.935 68.8875 114.531 68.8875 115.212C68.8875 115.888 68.7625 116.479 68.5125 116.985C68.2653 117.491 67.9187 117.884 67.4727 118.165C67.0295 118.447 66.5125 118.587 65.9216 118.587ZM65.9216 117.684C66.3705 117.684 66.7398 117.569 67.0295 117.339C67.3193 117.109 67.5338 116.806 67.673 116.431C67.8122 116.056 67.8818 115.65 67.8818 115.212C67.8818 114.775 67.8122 114.367 67.673 113.989C67.5338 113.612 67.3193 113.306 67.0295 113.073C66.7398 112.84 66.3705 112.724 65.9216 112.724C65.4727 112.724 65.1034 112.84 64.8136 113.073C64.5239 113.306 64.3094 113.612 64.1702 113.989C64.031 114.367 63.9614 114.775 63.9614 115.212C63.9614 115.65 64.031 116.056 64.1702 116.431C64.3094 116.806 64.5239 117.109 64.8136 117.339C65.1034 117.569 65.4727 117.684 65.9216 117.684ZM73.7977 118.451V111.906H74.7692V112.894H74.8374C74.9567 112.57 75.1727 112.308 75.4852 112.106C75.7977 111.904 76.1499 111.803 76.542 111.803C76.6158 111.803 76.7082 111.805 76.819 111.808C76.9298 111.81 77.0136 111.815 77.0704 111.82V112.843C77.0363 112.835 76.9582 112.822 76.836 112.805C76.7167 112.785 76.5903 112.775 76.4567 112.775C76.1386 112.775 75.8545 112.842 75.6045 112.975C75.3573 113.106 75.1613 113.288 75.0164 113.521C74.8744 113.751 74.8033 114.013 74.8033 114.309V118.451H73.7977ZM82.3875 115.775V111.906H83.3932V118.451H82.3875V117.343H82.3193C82.1659 117.675 81.9273 117.958 81.6034 118.191C81.2795 118.421 80.8705 118.536 80.3761 118.536C79.967 118.536 79.6034 118.447 79.2852 118.268C78.967 118.086 78.717 117.813 78.5352 117.45C78.3534 117.083 78.2625 116.621 78.2625 116.065V111.906H79.2682V115.996C79.2682 116.474 79.4017 116.854 79.6687 117.138C79.9386 117.423 80.2824 117.565 80.7 117.565C80.95 117.565 81.2043 117.501 81.4628 117.373C81.7241 117.245 81.9429 117.049 82.119 116.785C82.298 116.521 82.3875 116.184 82.3875 115.775ZM86.2408 109.724V118.451H85.2352V109.724H86.2408ZM90.8271 118.587C90.1964 118.587 89.6524 118.448 89.195 118.17C88.7405 117.888 88.3896 117.496 88.1425 116.994C87.8981 116.488 87.776 115.9 87.776 115.229C87.776 114.559 87.8981 113.968 88.1425 113.457C88.3896 112.942 88.7334 112.542 89.1737 112.255C89.6169 111.965 90.1339 111.82 90.7249 111.82C91.0658 111.82 91.4024 111.877 91.7348 111.991C92.0672 112.104 92.3697 112.289 92.6425 112.545C92.9152 112.798 93.1325 113.133 93.2945 113.55C93.4564 113.968 93.5374 114.482 93.5374 115.093V115.519H88.4919V114.65H92.5146C92.5146 114.281 92.4408 113.951 92.293 113.661C92.1481 113.371 91.9408 113.143 91.6709 112.975C91.4038 112.808 91.0885 112.724 90.7249 112.724C90.3243 112.724 89.9777 112.823 89.6851 113.022C89.3953 113.218 89.1723 113.474 89.016 113.789C88.8598 114.104 88.7817 114.442 88.7817 114.803V115.383C88.7817 115.877 88.8669 116.296 89.0374 116.64C89.2106 116.981 89.4507 117.241 89.7575 117.42C90.0643 117.596 90.4209 117.684 90.8271 117.684C91.0913 117.684 91.33 117.647 91.543 117.573C91.7589 117.496 91.945 117.383 92.1013 117.232C92.2575 117.079 92.3783 116.888 92.4635 116.661L93.4351 116.934C93.3328 117.263 93.1609 117.553 92.9195 117.803C92.678 118.05 92.3797 118.244 92.0246 118.383C91.6695 118.519 91.2703 118.587 90.8271 118.587Z"
fill="black" />
<path fill-rule="evenodd" clip-rule="evenodd"
d="M198.659 8L169.27 98.451L246.212 42.549H151.106L228.048 98.451L198.659 8Z" fill="#FF7F00" fill-opacity="0.25"
stroke="black" />
<path
d="M159.716 118.451V109.724H164.983V110.661H160.773V113.61H164.71V114.548H160.773V117.513H165.051V118.451H159.716ZM172.104 111.906L169.683 118.451H168.66L166.24 111.906H167.331L169.138 117.121H169.206L171.013 111.906H172.104ZM175.941 118.587C175.31 118.587 174.766 118.448 174.309 118.17C173.854 117.888 173.503 117.496 173.256 116.994C173.012 116.488 172.89 115.9 172.89 115.229C172.89 114.559 173.012 113.968 173.256 113.457C173.503 112.942 173.847 112.542 174.287 112.255C174.731 111.965 175.248 111.82 175.839 111.82C176.18 111.82 176.516 111.877 176.849 111.991C177.181 112.104 177.484 112.289 177.756 112.545C178.029 112.798 178.246 113.133 178.408 113.55C178.57 113.968 178.651 114.482 178.651 115.093V115.519H173.606V114.65H177.628C177.628 114.281 177.555 113.951 177.407 113.661C177.262 113.371 177.055 113.143 176.785 112.975C176.518 112.808 176.202 112.724 175.839 112.724C175.438 112.724 175.091 112.823 174.799 113.022C174.509 113.218 174.286 113.474 174.13 113.789C173.974 114.104 173.895 114.442 173.895 114.803V115.383C173.895 115.877 173.981 116.296 174.151 116.64C174.324 116.981 174.564 117.241 174.871 117.42C175.178 117.596 175.535 117.684 175.941 117.684C176.205 117.684 176.444 117.647 176.657 117.573C176.873 117.496 177.059 117.383 177.215 117.232C177.371 117.079 177.492 116.888 177.577 116.661L178.549 116.934C178.447 117.263 178.275 117.553 178.033 117.803C177.792 118.05 177.493 118.244 177.138 118.383C176.783 118.519 176.384 118.587 175.941 118.587ZM181.187 114.513V118.451H180.181V111.906H181.153V112.928H181.238C181.391 112.596 181.624 112.329 181.937 112.127C182.249 111.923 182.653 111.82 183.147 111.82C183.59 111.82 183.978 111.911 184.31 112.093C184.643 112.272 184.901 112.545 185.086 112.911C185.27 113.275 185.363 113.735 185.363 114.292V118.451H184.357V114.36C184.357 113.846 184.224 113.445 183.957 113.158C183.689 112.869 183.323 112.724 182.857 112.724C182.536 112.724 182.249 112.793 181.996 112.933C181.746 113.072 181.549 113.275 181.404 113.542C181.259 113.809 181.187 114.133 181.187 114.513ZM190.95 114.241V115.178H187.132V114.241H190.95ZM195.379 118.587C194.788 118.587 194.27 118.447 193.824 118.165C193.381 117.884 193.034 117.491 192.784 116.985C192.537 116.479 192.413 115.888 192.413 115.212C192.413 114.531 192.537 113.935 192.784 113.427C193.034 112.918 193.381 112.523 193.824 112.242C194.27 111.961 194.788 111.82 195.379 111.82C195.97 111.82 196.487 111.961 196.93 112.242C197.376 112.523 197.723 112.918 197.97 113.427C198.22 113.935 198.345 114.531 198.345 115.212C198.345 115.888 198.22 116.479 197.97 116.985C197.723 117.491 197.376 117.884 196.93 118.165C196.487 118.447 195.97 118.587 195.379 118.587ZM195.379 117.684C195.828 117.684 196.197 117.569 196.487 117.339C196.777 117.109 196.991 116.806 197.131 116.431C197.27 116.056 197.339 115.65 197.339 115.212C197.339 114.775 197.27 114.367 197.131 113.989C196.991 113.612 196.777 113.306 196.487 113.073C196.197 112.84 195.828 112.724 195.379 112.724C194.93 112.724 194.561 112.84 194.271 113.073C193.981 113.306 193.767 113.612 193.628 113.989C193.488 114.367 193.419 114.775 193.419 115.212C193.419 115.65 193.488 116.056 193.628 116.431C193.767 116.806 193.981 117.109 194.271 117.339C194.561 117.569 194.93 117.684 195.379 117.684ZM202.352 118.587C201.806 118.587 201.325 118.45 200.907 118.174C200.49 117.896 200.163 117.504 199.927 116.998C199.691 116.489 199.573 115.888 199.573 115.195C199.573 114.508 199.691 113.911 199.927 113.406C200.163 112.9 200.491 112.509 200.911 112.234C201.332 111.958 201.818 111.82 202.369 111.82C202.795 111.82 203.132 111.891 203.379 112.033C203.629 112.173 203.819 112.332 203.95 112.511C204.083 112.687 204.187 112.832 204.261 112.945H204.346V109.724H205.352V118.451H204.38V117.445H204.261C204.187 117.565 204.082 117.715 203.946 117.897C203.809 118.076 203.615 118.237 203.362 118.379C203.109 118.518 202.772 118.587 202.352 118.587ZM202.488 117.684C202.892 117.684 203.232 117.579 203.511 117.369C203.789 117.156 204.001 116.862 204.146 116.487C204.291 116.109 204.363 115.673 204.363 115.178C204.363 114.69 204.292 114.262 204.15 113.896C204.008 113.526 203.798 113.239 203.519 113.035C203.241 112.827 202.897 112.724 202.488 112.724C202.062 112.724 201.707 112.833 201.423 113.052C201.142 113.268 200.93 113.562 200.788 113.934C200.649 114.303 200.579 114.718 200.579 115.178C200.579 115.644 200.65 116.067 200.792 116.448C200.937 116.826 201.15 117.127 201.431 117.352C201.715 117.573 202.068 117.684 202.488 117.684ZM209.805 118.587C209.259 118.587 208.778 118.45 208.36 118.174C207.943 117.896 207.616 117.504 207.38 116.998C207.144 116.489 207.026 115.888 207.026 115.195C207.026 114.508 207.144 113.911 207.38 113.406C207.616 112.9 207.944 112.509 208.365 112.234C208.785 111.958 209.271 111.82 209.822 111.82C210.248 111.82 210.585 111.891 210.832 112.033C211.082 112.173 211.272 112.332 211.403 112.511C211.536 112.687 211.64 112.832 211.714 112.945H211.799V109.724H212.805V118.451H211.833V117.445H211.714C211.64 117.565 211.535 117.715 211.399 117.897C211.262 118.076 211.068 118.237 210.815 118.379C210.562 118.518 210.225 118.587 209.805 118.587ZM209.941 117.684C210.345 117.684 210.686 117.579 210.964 117.369C211.242 117.156 211.454 116.862 211.599 116.487C211.744 116.109 211.816 115.673 211.816 115.178C211.816 114.69 211.745 114.262 211.603 113.896C211.461 113.526 211.251 113.239 210.973 113.035C210.694 112.827 210.35 112.724 209.941 112.724C209.515 112.724 209.16 112.833 208.876 113.052C208.595 113.268 208.383 113.562 208.241 113.934C208.102 114.303 208.032 114.718 208.032 115.178C208.032 115.644 208.103 116.067 208.245 116.448C208.39 116.826 208.603 117.127 208.884 117.352C209.169 117.573 209.521 117.684 209.941 117.684ZM218.161 118.451V111.906H219.133V112.894H219.201C219.321 112.57 219.536 112.308 219.849 112.106C220.161 111.904 220.514 111.803 220.906 111.803C220.98 111.803 221.072 111.805 221.183 111.808C221.294 111.81 221.377 111.815 221.434 111.82V112.843C221.4 112.835 221.322 112.822 221.2 112.805C221.08 112.785 220.954 112.775 220.821 112.775C220.502 112.775 220.218 112.842 219.968 112.975C219.721 113.106 219.525 113.288 219.38 113.521C219.238 113.751 219.167 114.013 219.167 114.309V118.451H218.161ZM226.751 115.775V111.906H227.757V118.451H226.751V117.343H226.683C226.53 117.675 226.291 117.958 225.967 118.191C225.643 118.421 225.234 118.536 224.74 118.536C224.331 118.536 223.967 118.447 223.649 118.268C223.331 118.086 223.081 117.813 222.899 117.45C222.717 117.083 222.626 116.621 222.626 116.065V111.906H223.632V115.996C223.632 116.474 223.765 116.854 224.033 117.138C224.302 117.423 224.646 117.565 225.064 117.565C225.314 117.565 225.568 117.501 225.827 117.373C226.088 117.245 226.307 117.049 226.483 116.785C226.662 116.521 226.751 116.184 226.751 115.775ZM230.605 109.724V118.451H229.599V109.724H230.605ZM235.191 118.587C234.56 118.587 234.016 118.448 233.559 118.17C233.104 117.888 232.753 117.496 232.506 116.994C232.262 116.488 232.14 115.9 232.14 115.229C232.14 114.559 232.262 113.968 232.506 113.457C232.753 112.942 233.097 112.542 233.537 112.255C233.981 111.965 234.498 111.82 235.089 111.82C235.43 111.82 235.766 111.877 236.099 111.991C236.431 112.104 236.734 112.289 237.006 112.545C237.279 112.798 237.496 113.133 237.658 113.55C237.82 113.968 237.901 114.482 237.901 115.093V115.519H232.856V114.65H236.878C236.878 114.281 236.805 113.951 236.657 113.661C236.512 113.371 236.305 113.143 236.035 112.975C235.768 112.808 235.452 112.724 235.089 112.724C234.688 112.724 234.341 112.823 234.049 113.022C233.759 113.218 233.536 113.474 233.38 113.789C233.224 114.104 233.145 114.442 233.145 114.803V115.383C233.145 115.877 233.231 116.296 233.401 116.64C233.574 116.981 233.814 117.241 234.121 117.42C234.428 117.596 234.785 117.684 235.191 117.684C235.455 117.684 235.694 117.647 235.907 117.573C236.123 117.496 236.309 117.383 236.465 117.232C236.621 117.079 236.742 116.888 236.827 116.661L237.799 116.934C237.697 117.263 237.525 117.553 237.283 117.803C237.042 118.05 236.743 118.244 236.388 118.383C236.033 118.519 235.634 118.587 235.191 118.587Z"
fill="black" />
</svg>
</div>
<p><strong>Usage:</strong> Select a vector object, Modify fill rules: Click on a fill to toggle between non-zero and
even-odd. Adjust path orientation: Click on a loop to reverse it (helps with hole appearance). Set first node: Click
on any node to designate it as the first (appears as a small diamond)</p>
<p><strong>Benefits:</strong> Ensure compatibility with formats like TrueType fonts and Android VectorDrawable
(non-zero fill rule only).
Optimize designs for variable fonts (e.g., SF Symbols) by controlling first node placement.
Fine-tune complex vector shapes for precise control over fill behavior.
</p>
<p>This plugin empowers designers to manipulate vector paths with precision, improving compatibility and enabling
advanced design techniques.</p>
<p>
<strong>Acknowledgment:</strong> Special thanks to <a class="external-link" href="https://www.figma.com/@evan">Evan Wallace</a> for the original
<a class="external-link" href="https://www.figma.com/community/plugin/771155994770327940/fill-rule-editor">"Fill
Rule Editor"</a> plugin on which this work is based.
</p>
<div class="contact-section">
<h2>Stay Connected</h2>
<p>For updates and more information:</p>
<ul>
<li>Follow me on X: <a class="external-link" href="https://x.com/Autre_planete">@Autre_planete</a></li>
<li>Contact me: <a class="external-link" href="https://anotherplanet.io">AnotherPlanet.io</a></li>
</ul>
</div>
<div class="contact-section">
<h2>Links</h2>
<ul>
<li><a class="external-link"
href="https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-rule">https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-rule</a>
</li>
<li><a class="external-link"
href="https://www.sitepoint.com/understanding-svg-fill-rule-property/">https://www.sitepoint.com/understanding-svg-fill-rule-property/</a>
</li>
<li><a class="external-link"
href="https://oreillymedia.github.io/Using_SVG/extras/ch06-fill-rule.html">https://oreillymedia.github.io/Using_SVG/extras/ch06-fill-rule.html</a>
</li>
</ul>
</div>
<p>This project is an open source plugins.</p>
</div>
<script>
// Select the canvas element and get 2D rendering context
const canvas = document.querySelector('canvas');
const c = canvas.getContext('2d');
// Declare variables for node, hover state, and previous mouse position
let node;
let hover;
let prevMouse;
// Define colors for different visual elements
let nonzeroColor = 'rgba(63, 159, 255, 0.25)';
let evenoddColor = 'rgba(255, 127, 0, 0.25)';
let hoverNonzeroColor = nonzeroColor;
let hoverEvenoddColor = evenoddColor;
let geometryColor = '#777';
let hoverGeometryColor = '#000';
let firstGeometryColor = '#f00';
// Create diagonal pattern for nonzero and evenodd colors using SVG data
createDiagonalPattern(nonzeroColor, x => nonzeroColor = x);
createDiagonalPattern(evenoddColor, x => evenoddColor = x);
// Function to create diagonal pattern using SVG data
function createDiagonalPattern(color, callback) {
const img = new Image;
img.onload = () => callback(c.createPattern(img, 'repeat'));
img.src = `data:image/svg+xml;base64,` + btoa(`
<svg xmlns="http://www.w3.org/2000/svg" width="12" height="12">
<path fill="${color}" d="M0 4V0H4L12 8V12H8L0 4M12 4V0H8L12 4M0 12V8L4 12H0Z" />
</svg>
`);
}
// Check if a segment has a specific vertex index
function hasVertex(seg, index) {
return seg.start === index || seg.end === index;
}
// Determine the starting vertex of a network loop
function startingVertex(network, loop) {
const { segments } = network;
// Special-case a loop consisting of a cubic segment that comes out from and
// back to the same vertex
if (loop.length === 1) {
return segments[loop[0]].start;
}
// Make sure that the traversal works correctly even if the first segment
// is oriented backwards compared to the second one
const first = segments[loop[0]];
const second = segments[loop[1]];
let takeStart = hasVertex(second, first.end);
let takeEnd = hasVertex(second, first.start);
// There can be a figure 8 type arrangement, where the first and second
// segments form a loop, but continues on
if (takeStart && takeEnd && loop.length > 2) {
const third = segments[loop[2]];
takeStart = hasVertex(third, first.start);
}
if (takeStart) return first.start;
if (takeEnd) return first.end;
throw new Error('Failure in startingVertex');
}
function calculateWindingNumber(loop, vertices) {
let windingNumber = 0;
const n = loop.length;
for (let i = 0; i < n; i++) {
const { x: x1, y: y1 } = vertices[loop[i]];
const { x: x2, y: y2 } = vertices[loop[(i + 1) % n]];
if (y1 <= 0) {
if (y2 > 0 && (x1 * y2 - x2 * y1) > 0) {
windingNumber += 1;
}
} else {
if (y2 <= 0 && (x1 * y2 - x2 * y1) < 0) {
windingNumber -= 1;
}
}
}
return windingNumber;
}
function determinePathDirection(loop, vertices) {
const windingNumber = calculateWindingNumber(loop, vertices);
if (windingNumber > 0) {
return 1; // Clockwise
} else if (windingNumber < 0) {
return -1; // Counterclockwise
} else {
return 0; // Indeterminate
}
}
// Get tangent vector for a specific vertex index in a segment
function tangentForVertex(segment, index) {
if (index === segment.start) return segment.tangentStart;
if (index === segment.end) return segment.tangentEnd;
throw new Error('Failure in tangentForVertex');
}
// Get the other vertex index in a segment given one vertex index
function otherVertex(segment, index) {
if (index === segment.start) return segment.end;
if (index === segment.end) return segment.start;
throw new Error('Failure in otherVertex');
}
// Generator function to iterate through segments in a loop (iterator)
function* segmentIterator(network, loop) {
const { vertices, segments } = network;
let start = startingVertex(network, loop);
for (const index of loop) {
const segment = segments[index];
const end = otherVertex(segment, start);
const ts = tangentForVertex(segment, start);
const te = tangentForVertex(segment, end);
const s = vertices[start];
const e = vertices[end];
const cs = { x: s.x + ts.x, y: s.y + ts.y };
const ce = { x: e.x + te.x, y: e.y + te.y };
start = end;
yield { s, cs, ce, e };
}
}
// Function to outline a loop using cubic Bezier curves
function outlineLoop(tx, ty, network, loop) {
let isFirst = true;
for (const { s, cs, ce, e } of segmentIterator(network, loop)) {
if (isFirst) {
isFirst = false;
c.moveTo(tx(s.x), ty(s.y));
}
c.bezierCurveTo(tx(cs.x), ty(cs.y), tx(ce.x), ty(ce.y), tx(e.x), ty(e.y));
}
}
// Calculate cubic Bezier curve value at a specific parameter t
function cubic1D(a, b, c, d, t) {
const s = 1 - t;
return a * s * s * s + b * 3 * s * s * t + c * 3 * s * t * t + d * t * t * t;
}
// Calculate derivative of cubic Bezier curve at a specific parameter t
function cubicDerivative1D(a, b, c, d, t) {
const s = 1 - t;
return (b - a) * 3 * s * s + (c - b) * 6 * s * t + (d - c) * 3 * t * t;
}
// Create a bounding box object with methods to include points and check containment t
function createBoundingBox() {
return {
xmin: Infinity,
ymin: Infinity,
xmax: -Infinity,
ymax: -Infinity,
includePoint(x, y) {
this.xmin = Math.min(this.xmin, x);
this.ymin = Math.min(this.ymin, y);
this.xmax = Math.max(this.xmax, x);
this.ymax = Math.max(this.ymax, y);
},
containsPoint(point, threshold) {
return (
point.x >= this.xmin - threshold &&
point.y >= this.ymin - threshold &&
point.x <= this.xmax + threshold &&
point.y <= this.ymax + threshold
);
},
};
}
// Draw a vertex as a filled circle
function drawVertex(x, y, color) {
const size = 2; // Size of the circle
c.fillStyle = color;
c.beginPath();
c.arc(x, y, size, 0, Math.PI * size, false);
c.fill();
}
// Draw a vertex as a diamond circle
function drawFirstVertex(x, y, color) {
const size = 4; // Size of the diamond
c.fillStyle = color;
c.beginPath();
c.moveTo(x, y - size);
c.lineTo(x + size, y);
c.lineTo(x, y + size);
c.lineTo(x - size, y);
c.closePath();
c.fill();
}
// Draw a Bezier curve segment
function drawSegment(sx, sy, csx, csy, cex, cey, ex, ey, color) {
c.strokeStyle = color;
c.beginPath();
c.moveTo(sx, sy);
c.bezierCurveTo(csx, csy, cex, cey, ex, ey);
c.stroke();
}
// Create transformers for coordinate transformations based on network data
function createTransformers(network) {
const bb = createBoundingBox();
for (let { x, y } of network.vertices) {
bb.includePoint(x, y);
}
for (let { start: s, end: e, tangentStart: ts, tangentEnd: te } of network.segments) {
let { x: sx, y: sy } = network.vertices[s];
let { x: ex, y: ey } = network.vertices[e];
bb.includePoint(sx + ts.x, sy + ts.y);
bb.includePoint(ex + te.x, ey + te.y);
}
const adjustedHeight = innerHeight - 100;
const cx = (bb.xmin + bb.xmax) / 2;
const cy = (bb.ymin + bb.ymax) / 2;
const scale = Math.min(
(innerWidth - 40) / (bb.xmax - bb.xmin || 1),
(adjustedHeight - 40) / (bb.ymax - bb.ymin || 1));
function tx(x) { return innerWidth / 2 + (x - cx) * scale; }
function ty(y) { return adjustedHeight / 2 + (y - cy) * scale; }
function t({ x, y }) { return { x: tx(x), y: ty(y) }; }
return { t, tx, ty };
}
// Main draw function to render the vector network on canvas
function draw() {
const ratio = devicePixelRatio;
canvas.width = Math.round(ratio * innerWidth);
canvas.height = Math.round(ratio * innerHeight);
c.scale(ratio, ratio);
// If no node is selected, display instructions
if (!node) {
canvas.style.display = 'none';
instructions.style.display = 'block';
return;
}
// Otherwise, display canvas and hide instructions
canvas.style.display = 'block';
instructions.style.display = 'none';
const network = node.vectorNetwork;
const { tx, ty } = createTransformers(network);
// Render regions with filled paths based on winding rule
let regionIndex = 0;
for (const { windingRule, loops } of network.regions) {
const isHovered = hover &&
hover.type === 'region' &&
hover.regionIndex === regionIndex;
if (isHovered) {
c.fillStyle = windingRule === 'NONZERO' ? hoverNonzeroColor : hoverEvenoddColor;
} else {
c.fillStyle = windingRule === 'NONZERO' ? nonzeroColor : evenoddColor;
}
c.beginPath();
for (const loop of loops) {
outlineLoop(tx, ty, network, loop);
}
c.fill(windingRule.toLowerCase());
regionIndex++;
}
// Render segments with Bezier curves
for (const { start: s, end: e, tangentStart: ts, tangentEnd: te } of network.segments) {
const { x: sx, y: sy } = network.vertices[s];
const { x: ex, y: ey } = network.vertices[e];
drawSegment(
tx(sx), ty(sy),
tx(sx + ts.x), ty(sy + ts.y),
tx(ex + te.x), ty(ey + te.y),
tx(ex), ty(ey),
geometryColor);
}
// Loop winding order
regionIndex = 0;
for (const { windingRule, loops } of network.regions) {
let loopIndex = 0;
for (const loop of loops) {
const direction = determinePathDirection(loop, network.vertices);
const firstVertexIndex = direction === -1 ? loop[1] : loop[loop.length - 1];
const fv = network.vertices[firstVertexIndex]; // network.vertices[startingVertex(network, loop)];
drawFirstVertex(tx(fv.x), ty(fv.y), firstGeometryColor);
for (const vi of loop) {
const cv = network.vertices[vi];
const isHoveredVertex = hover && hover.type === 'vertex' && hover.vertexIndex === vi;
if (isHoveredVertex) {
drawFirstVertex(tx(cv.x), ty(cv.y), hoverGeometryColor);
} else {
drawVertex(tx(cv.x), ty(cv.y), geometryColor);
}
}
const isHovered = hover &&
hover.type === 'loop' &&
hover.regionIndex === regionIndex &&
hover.loopIndex === loopIndex;
c.fillStyle = isHovered ? hoverGeometryColor : geometryColor;
for (const { s, cs, ce, e } of segmentIterator(network, loop)) {
const t = 0.5;
const x = tx(cubic1D(s.x, cs.x, ce.x, e.x, t));
const y = ty(cubic1D(s.y, cs.y, ce.y, e.y, t));
let dx = cubicDerivative1D(s.x, cs.x, ce.x, e.x, t);
let dy = cubicDerivative1D(s.y, cs.y, ce.y, e.y, t);
const len = Math.sqrt(dx * dx + dy * dy);
dx *= 3 / len;
dy *= 3 / len;
c.beginPath();
c.moveTo(x + dx, y + dy);
c.lineTo(x - dx - dy, y - dy + dx);
c.lineTo(x - dx + dy, y - dy - dx);
c.fill();
if (isHovered) {
drawSegment(
tx(s.x), ty(s.y),
tx(cs.x), ty(cs.y),
tx(ce.x), ty(ce.y),
tx(e.x), ty(e.y),
hoverGeometryColor);
drawVertex(tx(s.x), ty(s.y), hoverGeometryColor);
drawVertex(tx(e.x), ty(e.y), hoverGeometryColor);
}
}
loopIndex++;
}
regionIndex++;
}
// Legend
const legendX = 150;
const legendY = innerHeight - 80;
const boxSize = 20;
const spacing = 8;
c.strokeStyle = 'black';
c.fillStyle = nonzeroColor;
c.beginPath();
c.rect(legendX, legendY, boxSize, boxSize);
c.fill();
c.stroke();
c.fillStyle = evenoddColor;
c.beginPath();
c.rect(legendX, legendY + boxSize + spacing, boxSize, boxSize);
c.fill();
c.stroke();
c.fillStyle = 'black';
c.font = '11px Inter, sans-serif';
c.textAlign = 'left';
c.textBaseline = 'middle';
c.fillText('Non-zero rule',
legendX + boxSize + spacing,
legendY + boxSize / 2);
c.fillText('Even-odd rule',
legendX + boxSize + spacing,
legendY + boxSize * 1.5 + spacing);
}
function midpoint(a, b) {
return { x: (a.x + b.x) / 2, y: (a.y + b.y) / 2 };
}
function subdivideCubic(a, b, c, d) {
const ab = midpoint(a, b);
const bc = midpoint(b, c);
const cd = midpoint(c, d);
const abc = midpoint(ab, bc);
const bcd = midpoint(bc, cd);
const abcd = midpoint(abc, bcd);
return [[a, ab, abc, abcd], [abcd, bcd, cd, d]];
}
function isPointNearCubic(s, cs, ce, e, point, threshold) {
const bb = createBoundingBox();
bb.includePoint(s.x, s.y);
bb.includePoint(cs.x, cs.y);
bb.includePoint(ce.x, ce.y);
bb.includePoint(e.x, e.y);
// Culling
if (!bb.containsPoint(point, threshold)) {
return false;
}
function visit(s, cs, ce, e, depth) {
// Subdivision
if (depth < 4) {
const [[s1, cs1, ce1, e1], [s2, cs2, ce2, e2]] = subdivideCubic(s, cs, ce, e);
return visit(s1, cs1, ce1, e1, depth + 1) || visit(s2, cs2, ce2, e2, depth + 1);
}
// Closest point to line
const xse = e.x - s.x;
const yse = e.y - s.y;
const xsp = point.x - s.x;
const ysp = point.y - s.y;
let t = (xse * xsp + yse * ysp) / (xse * xse + yse * yse);
t = Math.max(0, Math.min(1, t));
// Distance check
const dx = s.x + xse * t - point.x;
const dy = s.y + yse * t - point.y;
return dx * dx + dy * dy < threshold * threshold;
}
return visit(s, cs, ce, e, 0);
}
function isPointInsideRegion(t, tx, ty, network, region, point) {
const bb = createBoundingBox();
for (const loop of region.loops) {
for (const { s, cs, ce, e } of segmentIterator(network, loop)) {
bb.includePoint(tx(s.x), ty(s.y));
bb.includePoint(tx(cs.x), ty(cs.y));
bb.includePoint(tx(ce.x), ty(ce.y));
bb.includePoint(tx(e.x), ty(e.y));
}
}
// Culling
if (!bb.containsPoint(point, 0)) {
return false;
}
function visit(s, cs, ce, e, depth) {
const xmax = Math.max(s.x, cs.x, ce.x, e.x);
const ymin = Math.min(s.y, cs.y, ce.y, e.y);
const ymax = Math.max(s.y, cs.y, ce.y, e.y);
// Culling
if (point.x > xmax || point.y < ymin || point.y > ymax) {
return;
}
// Subdivision
if (depth < 4) {
const [[s1, cs1, ce1, e1], [s2, cs2, ce2, e2]] = subdivideCubic(s, cs, ce, e);
visit(s1, cs1, ce1, e1, depth + 1);
visit(s2, cs2, ce2, e2, depth + 1);
return;
}
// Upward crossing
if (s.y <= point.y && point.y < e.y) {
const cross = (point.y - s.y) * (e.x - s.x) - (point.x - s.x) * (e.y - s.y);
if (cross > 0) {
crossingCount++;
}
}
// Downward crossing
else if (e.y <= point.y && point.y < s.y) {
const cross = (point.y - s.y) * (e.x - s.x) - (point.x - s.x) * (e.y - s.y);
if (cross < 0) {
crossingCount--;
}
}
}
let crossingCount = 0;
for (const loop of region.loops) {
for (const { s, cs, ce, e } of segmentIterator(network, loop)) {
visit(t(s), t(cs), t(ce), t(e), 0);
}
}
if (region.windingRule === 'EVENODD') {
crossingCount &= 1;
}
return crossingCount !== 0;
}
function reorderLoop(loop, vertexIndex, direction) {
const index = loop.indexOf(vertexIndex);
if (index === -1) return loop; // Return original loop if startIndex not found
if (direction === -1) {
return [...loop.slice(index), ...loop.slice(0, index)];
} else {
return [
...loop.slice(index + 1), // Elements after the item
...loop.slice(0, index), // Elements before the item
vertexIndex // The item itself
];
}
}
function hitTest(point) {
prevMouse = point;
if (!node) {
return null;
}
const network = node.vectorNetwork;
const { t, tx, ty } = createTransformers(network);
function hitTestVertex(threshold) {
let regionIndex = 0;
for (const { windingRule, loops } of network.regions) {
let loopIndex = 0;
for (const loop of loops) {
for (const vertexIndex of loop) {
const v = network.vertices[vertexIndex];
if (v !== undefined) {
const dx = point.x - tx(v.x);
const dy = point.y - ty(v.y);
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance <= threshold) {
return { type: 'vertex', regionIndex, loopIndex, vertexIndex, v };
}
}
}
loopIndex++;
}
regionIndex++;
}
return null;
}
function hitTestLoops(threshold) {
let regionIndex = 0;
for (const { windingRule, loops } of network.regions) {
let loopIndex = 0;
for (const loop of loops) {
for (const { s, cs, ce, e } of segmentIterator(network, loop)) {
if (isPointNearCubic(t(s), t(cs), t(ce), t(e), point, threshold)) {
return { type: 'loop', regionIndex, loopIndex };
}
}
loopIndex++;
}
regionIndex++;
}
return null;
}
// Hit-test vertex
const vhit = hitTestVertex(4);
if (vhit !== null) {
return vhit;
}
// Hit-test loops
const hit = hitTestLoops(2);
if (hit !== null) {
return hit;
}
// Hit-test regions
let regionIndex = 0;
for (const region of network.regions) {
if (isPointInsideRegion(t, tx, ty, network, region, point)) {
return { type: 'region', regionIndex };
}
regionIndex++;
}
// Hit-test loops again
return hitTestLoops(5);
}
// Event listener for mouse movement to update hover state
onmousemove = ({ pageX, pageY }) => {
hover = hitTest({ x: pageX, y: pageY });
draw();
};
onmousedown = ({ pageX, pageY }) => {
if (!node?.vectorNetwork) {
return;
}
hover = hitTest({ x: pageX, y: pageY });
const { vertices, segments, regions } = { ...node.vectorNetwork };
// if hover node and shift is pressed
if (hover && hover.type === 'vertex') {
const currentLoop = regions[hover.regionIndex].loops[hover.loopIndex];
const dir = determinePathDirection(currentLoop, vertices);
const reorderedLoop = reorderLoop(currentLoop, hover.vertexIndex, dir);
node.vectorNetwork.regions[hover.regionIndex].loops[hover.loopIndex] = reorderedLoop;
parent.postMessage({ pluginMessage: { node } }, '*');
}
if (hover && hover.type === 'loop') {
const currentLoop = regions[hover.regionIndex].loops[hover.loopIndex];
node.vectorNetwork.regions[hover.regionIndex].loops[hover.loopIndex] = currentLoop.reverse();
parent.postMessage({ pluginMessage: { node } }, '*');
}
else if (hover && hover.type === 'region') {
const region = regions[hover.regionIndex];
region.windingRule = region.windingRule === 'NONZERO' ? 'EVENODD' : 'NONZERO';
node.vectorNetwork.regions[hover.regionIndex] = region;
parent.postMessage({ pluginMessage: { node } }, '*');
}
draw();
};
onmessage = ({ data: { pluginMessage } }) => {
if (pluginMessage) {
node = pluginMessage.node;
hover = prevMouse ? hitTest(prevMouse) : null;
draw();
}
};
if (document.fonts && document.fonts.ready) {
document.fonts.ready.then(draw);
} else {
draw();
}
document.querySelectorAll('.external-link').forEach(link => {
link.onclick = (event) => {
event.preventDefault(); // Prevent the default link behavior
const url = event.target.href;
parent.postMessage({ pluginMessage: { type: 'open-url', url: url } }, '*');
};
});
</script>