sketchmark 0.1.6 → 0.1.8

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/dist/index.cjs CHANGED
@@ -70,7 +70,7 @@ const ARROW_PATTERNS = ["<-->", "<->", "-->", "<--", "->", "<-", "---", "--"];
70
70
  // Characters that can start an arrow pattern — used to decide whether a '-'
71
71
  // inside an identifier is part of a kebab-case name or the start of an arrow.
72
72
  const ARROW_START_AFTER_DASH = new Set([">", "-", "."]);
73
- function tokenize(src) {
73
+ function tokenize$1(src) {
74
74
  const tokens = [];
75
75
  let i = 0, line = 1, lineStart = 0;
76
76
  const col = () => i - lineStart + 1;
@@ -293,7 +293,7 @@ function propsToStyle(p) {
293
293
  }
294
294
  function parse(src) {
295
295
  resetUid();
296
- const tokens = tokenize(src).filter((t) => t.type !== "NEWLINE" || t.value === "\n");
296
+ const tokens = tokenize$1(src).filter((t) => t.type !== "NEWLINE" || t.value === "\n");
297
297
  // Collapse multiple consecutive NEWLINEs into one
298
298
  const flat = [];
299
299
  let lastNL = false;
@@ -2764,6 +2764,2080 @@ function registerFont(name, family, url) {
2764
2764
  loadFont(name);
2765
2765
  }
2766
2766
 
2767
+ function rotatePoints(points, center, degrees) {
2768
+ if (points && points.length) {
2769
+ const [cx, cy] = center;
2770
+ const angle = (Math.PI / 180) * degrees;
2771
+ const cos = Math.cos(angle);
2772
+ const sin = Math.sin(angle);
2773
+ for (const p of points) {
2774
+ const [x, y] = p;
2775
+ p[0] = ((x - cx) * cos) - ((y - cy) * sin) + cx;
2776
+ p[1] = ((x - cx) * sin) + ((y - cy) * cos) + cy;
2777
+ }
2778
+ }
2779
+ }
2780
+ function rotateLines(lines, center, degrees) {
2781
+ const points = [];
2782
+ lines.forEach((line) => points.push(...line));
2783
+ rotatePoints(points, center, degrees);
2784
+ }
2785
+ function areSamePoints(p1, p2) {
2786
+ return p1[0] === p2[0] && p1[1] === p2[1];
2787
+ }
2788
+ function hachureLines(polygons, hachureGap, hachureAngle, hachureStepOffset = 1) {
2789
+ const angle = hachureAngle;
2790
+ const gap = Math.max(hachureGap, 0.1);
2791
+ const polygonList = (polygons[0] && polygons[0][0] && (typeof polygons[0][0] === 'number')) ? [polygons] : polygons;
2792
+ const rotationCenter = [0, 0];
2793
+ if (angle) {
2794
+ for (const polygon of polygonList) {
2795
+ rotatePoints(polygon, rotationCenter, angle);
2796
+ }
2797
+ }
2798
+ const lines = straightHachureLines(polygonList, gap, hachureStepOffset);
2799
+ if (angle) {
2800
+ for (const polygon of polygonList) {
2801
+ rotatePoints(polygon, rotationCenter, -angle);
2802
+ }
2803
+ rotateLines(lines, rotationCenter, -angle);
2804
+ }
2805
+ return lines;
2806
+ }
2807
+ function straightHachureLines(polygons, gap, hachureStepOffset) {
2808
+ const vertexArray = [];
2809
+ for (const polygon of polygons) {
2810
+ const vertices = [...polygon];
2811
+ if (!areSamePoints(vertices[0], vertices[vertices.length - 1])) {
2812
+ vertices.push([vertices[0][0], vertices[0][1]]);
2813
+ }
2814
+ if (vertices.length > 2) {
2815
+ vertexArray.push(vertices);
2816
+ }
2817
+ }
2818
+ const lines = [];
2819
+ gap = Math.max(gap, 0.1);
2820
+ // Create sorted edges table
2821
+ const edges = [];
2822
+ for (const vertices of vertexArray) {
2823
+ for (let i = 0; i < vertices.length - 1; i++) {
2824
+ const p1 = vertices[i];
2825
+ const p2 = vertices[i + 1];
2826
+ if (p1[1] !== p2[1]) {
2827
+ const ymin = Math.min(p1[1], p2[1]);
2828
+ edges.push({
2829
+ ymin,
2830
+ ymax: Math.max(p1[1], p2[1]),
2831
+ x: ymin === p1[1] ? p1[0] : p2[0],
2832
+ islope: (p2[0] - p1[0]) / (p2[1] - p1[1]),
2833
+ });
2834
+ }
2835
+ }
2836
+ }
2837
+ edges.sort((e1, e2) => {
2838
+ if (e1.ymin < e2.ymin) {
2839
+ return -1;
2840
+ }
2841
+ if (e1.ymin > e2.ymin) {
2842
+ return 1;
2843
+ }
2844
+ if (e1.x < e2.x) {
2845
+ return -1;
2846
+ }
2847
+ if (e1.x > e2.x) {
2848
+ return 1;
2849
+ }
2850
+ if (e1.ymax === e2.ymax) {
2851
+ return 0;
2852
+ }
2853
+ return (e1.ymax - e2.ymax) / Math.abs((e1.ymax - e2.ymax));
2854
+ });
2855
+ if (!edges.length) {
2856
+ return lines;
2857
+ }
2858
+ // Start scanning
2859
+ let activeEdges = [];
2860
+ let y = edges[0].ymin;
2861
+ let iteration = 0;
2862
+ while (activeEdges.length || edges.length) {
2863
+ if (edges.length) {
2864
+ let ix = -1;
2865
+ for (let i = 0; i < edges.length; i++) {
2866
+ if (edges[i].ymin > y) {
2867
+ break;
2868
+ }
2869
+ ix = i;
2870
+ }
2871
+ const removed = edges.splice(0, ix + 1);
2872
+ removed.forEach((edge) => {
2873
+ activeEdges.push({ s: y, edge });
2874
+ });
2875
+ }
2876
+ activeEdges = activeEdges.filter((ae) => {
2877
+ if (ae.edge.ymax <= y) {
2878
+ return false;
2879
+ }
2880
+ return true;
2881
+ });
2882
+ activeEdges.sort((ae1, ae2) => {
2883
+ if (ae1.edge.x === ae2.edge.x) {
2884
+ return 0;
2885
+ }
2886
+ return (ae1.edge.x - ae2.edge.x) / Math.abs((ae1.edge.x - ae2.edge.x));
2887
+ });
2888
+ // fill between the edges
2889
+ if ((hachureStepOffset !== 1) || (iteration % gap === 0)) {
2890
+ if (activeEdges.length > 1) {
2891
+ for (let i = 0; i < activeEdges.length; i = i + 2) {
2892
+ const nexti = i + 1;
2893
+ if (nexti >= activeEdges.length) {
2894
+ break;
2895
+ }
2896
+ const ce = activeEdges[i].edge;
2897
+ const ne = activeEdges[nexti].edge;
2898
+ lines.push([
2899
+ [Math.round(ce.x), y],
2900
+ [Math.round(ne.x), y],
2901
+ ]);
2902
+ }
2903
+ }
2904
+ }
2905
+ y += hachureStepOffset;
2906
+ activeEdges.forEach((ae) => {
2907
+ ae.edge.x = ae.edge.x + (hachureStepOffset * ae.edge.islope);
2908
+ });
2909
+ iteration++;
2910
+ }
2911
+ return lines;
2912
+ }
2913
+
2914
+ function polygonHachureLines(polygonList, o) {
2915
+ var _a;
2916
+ const angle = o.hachureAngle + 90;
2917
+ let gap = o.hachureGap;
2918
+ if (gap < 0) {
2919
+ gap = o.strokeWidth * 4;
2920
+ }
2921
+ gap = Math.round(Math.max(gap, 0.1));
2922
+ let skipOffset = 1;
2923
+ if (o.roughness >= 1) {
2924
+ if ((((_a = o.randomizer) === null || _a === void 0 ? void 0 : _a.next()) || Math.random()) > 0.7) {
2925
+ skipOffset = gap;
2926
+ }
2927
+ }
2928
+ return hachureLines(polygonList, gap, angle, skipOffset || 1);
2929
+ }
2930
+
2931
+ class HachureFiller {
2932
+ constructor(helper) {
2933
+ this.helper = helper;
2934
+ }
2935
+ fillPolygons(polygonList, o) {
2936
+ return this._fillPolygons(polygonList, o);
2937
+ }
2938
+ _fillPolygons(polygonList, o) {
2939
+ const lines = polygonHachureLines(polygonList, o);
2940
+ const ops = this.renderLines(lines, o);
2941
+ return { type: 'fillSketch', ops };
2942
+ }
2943
+ renderLines(lines, o) {
2944
+ const ops = [];
2945
+ for (const line of lines) {
2946
+ ops.push(...this.helper.doubleLineOps(line[0][0], line[0][1], line[1][0], line[1][1], o));
2947
+ }
2948
+ return ops;
2949
+ }
2950
+ }
2951
+
2952
+ function lineLength(line) {
2953
+ const p1 = line[0];
2954
+ const p2 = line[1];
2955
+ return Math.sqrt(Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2));
2956
+ }
2957
+
2958
+ class ZigZagFiller extends HachureFiller {
2959
+ fillPolygons(polygonList, o) {
2960
+ let gap = o.hachureGap;
2961
+ if (gap < 0) {
2962
+ gap = o.strokeWidth * 4;
2963
+ }
2964
+ gap = Math.max(gap, 0.1);
2965
+ const o2 = Object.assign({}, o, { hachureGap: gap });
2966
+ const lines = polygonHachureLines(polygonList, o2);
2967
+ const zigZagAngle = (Math.PI / 180) * o.hachureAngle;
2968
+ const zigzagLines = [];
2969
+ const dgx = gap * 0.5 * Math.cos(zigZagAngle);
2970
+ const dgy = gap * 0.5 * Math.sin(zigZagAngle);
2971
+ for (const [p1, p2] of lines) {
2972
+ if (lineLength([p1, p2])) {
2973
+ zigzagLines.push([
2974
+ [p1[0] - dgx, p1[1] + dgy],
2975
+ [...p2],
2976
+ ], [
2977
+ [p1[0] + dgx, p1[1] - dgy],
2978
+ [...p2],
2979
+ ]);
2980
+ }
2981
+ }
2982
+ const ops = this.renderLines(zigzagLines, o);
2983
+ return { type: 'fillSketch', ops };
2984
+ }
2985
+ }
2986
+
2987
+ class HatchFiller extends HachureFiller {
2988
+ fillPolygons(polygonList, o) {
2989
+ const set = this._fillPolygons(polygonList, o);
2990
+ const o2 = Object.assign({}, o, { hachureAngle: o.hachureAngle + 90 });
2991
+ const set2 = this._fillPolygons(polygonList, o2);
2992
+ set.ops = set.ops.concat(set2.ops);
2993
+ return set;
2994
+ }
2995
+ }
2996
+
2997
+ class DotFiller {
2998
+ constructor(helper) {
2999
+ this.helper = helper;
3000
+ }
3001
+ fillPolygons(polygonList, o) {
3002
+ o = Object.assign({}, o, { hachureAngle: 0 });
3003
+ const lines = polygonHachureLines(polygonList, o);
3004
+ return this.dotsOnLines(lines, o);
3005
+ }
3006
+ dotsOnLines(lines, o) {
3007
+ const ops = [];
3008
+ let gap = o.hachureGap;
3009
+ if (gap < 0) {
3010
+ gap = o.strokeWidth * 4;
3011
+ }
3012
+ gap = Math.max(gap, 0.1);
3013
+ let fweight = o.fillWeight;
3014
+ if (fweight < 0) {
3015
+ fweight = o.strokeWidth / 2;
3016
+ }
3017
+ const ro = gap / 4;
3018
+ for (const line of lines) {
3019
+ const length = lineLength(line);
3020
+ const dl = length / gap;
3021
+ const count = Math.ceil(dl) - 1;
3022
+ const offset = length - (count * gap);
3023
+ const x = ((line[0][0] + line[1][0]) / 2) - (gap / 4);
3024
+ const minY = Math.min(line[0][1], line[1][1]);
3025
+ for (let i = 0; i < count; i++) {
3026
+ const y = minY + offset + (i * gap);
3027
+ const cx = (x - ro) + Math.random() * 2 * ro;
3028
+ const cy = (y - ro) + Math.random() * 2 * ro;
3029
+ const el = this.helper.ellipse(cx, cy, fweight, fweight, o);
3030
+ ops.push(...el.ops);
3031
+ }
3032
+ }
3033
+ return { type: 'fillSketch', ops };
3034
+ }
3035
+ }
3036
+
3037
+ class DashedFiller {
3038
+ constructor(helper) {
3039
+ this.helper = helper;
3040
+ }
3041
+ fillPolygons(polygonList, o) {
3042
+ const lines = polygonHachureLines(polygonList, o);
3043
+ return { type: 'fillSketch', ops: this.dashedLine(lines, o) };
3044
+ }
3045
+ dashedLine(lines, o) {
3046
+ const offset = o.dashOffset < 0 ? (o.hachureGap < 0 ? (o.strokeWidth * 4) : o.hachureGap) : o.dashOffset;
3047
+ const gap = o.dashGap < 0 ? (o.hachureGap < 0 ? (o.strokeWidth * 4) : o.hachureGap) : o.dashGap;
3048
+ const ops = [];
3049
+ lines.forEach((line) => {
3050
+ const length = lineLength(line);
3051
+ const count = Math.floor(length / (offset + gap));
3052
+ const startOffset = (length + gap - (count * (offset + gap))) / 2;
3053
+ let p1 = line[0];
3054
+ let p2 = line[1];
3055
+ if (p1[0] > p2[0]) {
3056
+ p1 = line[1];
3057
+ p2 = line[0];
3058
+ }
3059
+ const alpha = Math.atan((p2[1] - p1[1]) / (p2[0] - p1[0]));
3060
+ for (let i = 0; i < count; i++) {
3061
+ const lstart = i * (offset + gap);
3062
+ const lend = lstart + offset;
3063
+ const start = [p1[0] + (lstart * Math.cos(alpha)) + (startOffset * Math.cos(alpha)), p1[1] + lstart * Math.sin(alpha) + (startOffset * Math.sin(alpha))];
3064
+ const end = [p1[0] + (lend * Math.cos(alpha)) + (startOffset * Math.cos(alpha)), p1[1] + (lend * Math.sin(alpha)) + (startOffset * Math.sin(alpha))];
3065
+ ops.push(...this.helper.doubleLineOps(start[0], start[1], end[0], end[1], o));
3066
+ }
3067
+ });
3068
+ return ops;
3069
+ }
3070
+ }
3071
+
3072
+ class ZigZagLineFiller {
3073
+ constructor(helper) {
3074
+ this.helper = helper;
3075
+ }
3076
+ fillPolygons(polygonList, o) {
3077
+ const gap = o.hachureGap < 0 ? (o.strokeWidth * 4) : o.hachureGap;
3078
+ const zo = o.zigzagOffset < 0 ? gap : o.zigzagOffset;
3079
+ o = Object.assign({}, o, { hachureGap: gap + zo });
3080
+ const lines = polygonHachureLines(polygonList, o);
3081
+ return { type: 'fillSketch', ops: this.zigzagLines(lines, zo, o) };
3082
+ }
3083
+ zigzagLines(lines, zo, o) {
3084
+ const ops = [];
3085
+ lines.forEach((line) => {
3086
+ const length = lineLength(line);
3087
+ const count = Math.round(length / (2 * zo));
3088
+ let p1 = line[0];
3089
+ let p2 = line[1];
3090
+ if (p1[0] > p2[0]) {
3091
+ p1 = line[1];
3092
+ p2 = line[0];
3093
+ }
3094
+ const alpha = Math.atan((p2[1] - p1[1]) / (p2[0] - p1[0]));
3095
+ for (let i = 0; i < count; i++) {
3096
+ const lstart = i * 2 * zo;
3097
+ const lend = (i + 1) * 2 * zo;
3098
+ const dz = Math.sqrt(2 * Math.pow(zo, 2));
3099
+ const start = [p1[0] + (lstart * Math.cos(alpha)), p1[1] + lstart * Math.sin(alpha)];
3100
+ const end = [p1[0] + (lend * Math.cos(alpha)), p1[1] + (lend * Math.sin(alpha))];
3101
+ const middle = [start[0] + dz * Math.cos(alpha + Math.PI / 4), start[1] + dz * Math.sin(alpha + Math.PI / 4)];
3102
+ ops.push(...this.helper.doubleLineOps(start[0], start[1], middle[0], middle[1], o), ...this.helper.doubleLineOps(middle[0], middle[1], end[0], end[1], o));
3103
+ }
3104
+ });
3105
+ return ops;
3106
+ }
3107
+ }
3108
+
3109
+ const fillers = {};
3110
+ function getFiller(o, helper) {
3111
+ let fillerName = o.fillStyle || 'hachure';
3112
+ if (!fillers[fillerName]) {
3113
+ switch (fillerName) {
3114
+ case 'zigzag':
3115
+ if (!fillers[fillerName]) {
3116
+ fillers[fillerName] = new ZigZagFiller(helper);
3117
+ }
3118
+ break;
3119
+ case 'cross-hatch':
3120
+ if (!fillers[fillerName]) {
3121
+ fillers[fillerName] = new HatchFiller(helper);
3122
+ }
3123
+ break;
3124
+ case 'dots':
3125
+ if (!fillers[fillerName]) {
3126
+ fillers[fillerName] = new DotFiller(helper);
3127
+ }
3128
+ break;
3129
+ case 'dashed':
3130
+ if (!fillers[fillerName]) {
3131
+ fillers[fillerName] = new DashedFiller(helper);
3132
+ }
3133
+ break;
3134
+ case 'zigzag-line':
3135
+ if (!fillers[fillerName]) {
3136
+ fillers[fillerName] = new ZigZagLineFiller(helper);
3137
+ }
3138
+ break;
3139
+ case 'hachure':
3140
+ default:
3141
+ fillerName = 'hachure';
3142
+ if (!fillers[fillerName]) {
3143
+ fillers[fillerName] = new HachureFiller(helper);
3144
+ }
3145
+ break;
3146
+ }
3147
+ }
3148
+ return fillers[fillerName];
3149
+ }
3150
+
3151
+ function randomSeed() {
3152
+ return Math.floor(Math.random() * 2 ** 31);
3153
+ }
3154
+ class Random {
3155
+ constructor(seed) {
3156
+ this.seed = seed;
3157
+ }
3158
+ next() {
3159
+ if (this.seed) {
3160
+ return ((2 ** 31 - 1) & (this.seed = Math.imul(48271, this.seed))) / 2 ** 31;
3161
+ }
3162
+ else {
3163
+ return Math.random();
3164
+ }
3165
+ }
3166
+ }
3167
+
3168
+ const COMMAND = 0;
3169
+ const NUMBER = 1;
3170
+ const EOD = 2;
3171
+ const PARAMS = { A: 7, a: 7, C: 6, c: 6, H: 1, h: 1, L: 2, l: 2, M: 2, m: 2, Q: 4, q: 4, S: 4, s: 4, T: 2, t: 2, V: 1, v: 1, Z: 0, z: 0 };
3172
+ function tokenize(d) {
3173
+ const tokens = new Array();
3174
+ while (d !== '') {
3175
+ if (d.match(/^([ \t\r\n,]+)/)) {
3176
+ d = d.substr(RegExp.$1.length);
3177
+ }
3178
+ else if (d.match(/^([aAcChHlLmMqQsStTvVzZ])/)) {
3179
+ tokens[tokens.length] = { type: COMMAND, text: RegExp.$1 };
3180
+ d = d.substr(RegExp.$1.length);
3181
+ }
3182
+ else if (d.match(/^(([-+]?[0-9]+(\.[0-9]*)?|[-+]?\.[0-9]+)([eE][-+]?[0-9]+)?)/)) {
3183
+ tokens[tokens.length] = { type: NUMBER, text: `${parseFloat(RegExp.$1)}` };
3184
+ d = d.substr(RegExp.$1.length);
3185
+ }
3186
+ else {
3187
+ return [];
3188
+ }
3189
+ }
3190
+ tokens[tokens.length] = { type: EOD, text: '' };
3191
+ return tokens;
3192
+ }
3193
+ function isType(token, type) {
3194
+ return token.type === type;
3195
+ }
3196
+ function parsePath(d) {
3197
+ const segments = [];
3198
+ const tokens = tokenize(d);
3199
+ let mode = 'BOD';
3200
+ let index = 0;
3201
+ let token = tokens[index];
3202
+ while (!isType(token, EOD)) {
3203
+ let paramsCount = 0;
3204
+ const params = [];
3205
+ if (mode === 'BOD') {
3206
+ if (token.text === 'M' || token.text === 'm') {
3207
+ index++;
3208
+ paramsCount = PARAMS[token.text];
3209
+ mode = token.text;
3210
+ }
3211
+ else {
3212
+ return parsePath('M0,0' + d);
3213
+ }
3214
+ }
3215
+ else if (isType(token, NUMBER)) {
3216
+ paramsCount = PARAMS[mode];
3217
+ }
3218
+ else {
3219
+ index++;
3220
+ paramsCount = PARAMS[token.text];
3221
+ mode = token.text;
3222
+ }
3223
+ if ((index + paramsCount) < tokens.length) {
3224
+ for (let i = index; i < index + paramsCount; i++) {
3225
+ const numbeToken = tokens[i];
3226
+ if (isType(numbeToken, NUMBER)) {
3227
+ params[params.length] = +numbeToken.text;
3228
+ }
3229
+ else {
3230
+ throw new Error('Param not a number: ' + mode + ',' + numbeToken.text);
3231
+ }
3232
+ }
3233
+ if (typeof PARAMS[mode] === 'number') {
3234
+ const segment = { key: mode, data: params };
3235
+ segments.push(segment);
3236
+ index += paramsCount;
3237
+ token = tokens[index];
3238
+ if (mode === 'M')
3239
+ mode = 'L';
3240
+ if (mode === 'm')
3241
+ mode = 'l';
3242
+ }
3243
+ else {
3244
+ throw new Error('Bad segment: ' + mode);
3245
+ }
3246
+ }
3247
+ else {
3248
+ throw new Error('Path data ended short');
3249
+ }
3250
+ }
3251
+ return segments;
3252
+ }
3253
+
3254
+ // Translate relative commands to absolute commands
3255
+ function absolutize(segments) {
3256
+ let cx = 0, cy = 0;
3257
+ let subx = 0, suby = 0;
3258
+ const out = [];
3259
+ for (const { key, data } of segments) {
3260
+ switch (key) {
3261
+ case 'M':
3262
+ out.push({ key: 'M', data: [...data] });
3263
+ [cx, cy] = data;
3264
+ [subx, suby] = data;
3265
+ break;
3266
+ case 'm':
3267
+ cx += data[0];
3268
+ cy += data[1];
3269
+ out.push({ key: 'M', data: [cx, cy] });
3270
+ subx = cx;
3271
+ suby = cy;
3272
+ break;
3273
+ case 'L':
3274
+ out.push({ key: 'L', data: [...data] });
3275
+ [cx, cy] = data;
3276
+ break;
3277
+ case 'l':
3278
+ cx += data[0];
3279
+ cy += data[1];
3280
+ out.push({ key: 'L', data: [cx, cy] });
3281
+ break;
3282
+ case 'C':
3283
+ out.push({ key: 'C', data: [...data] });
3284
+ cx = data[4];
3285
+ cy = data[5];
3286
+ break;
3287
+ case 'c': {
3288
+ const newdata = data.map((d, i) => (i % 2) ? (d + cy) : (d + cx));
3289
+ out.push({ key: 'C', data: newdata });
3290
+ cx = newdata[4];
3291
+ cy = newdata[5];
3292
+ break;
3293
+ }
3294
+ case 'Q':
3295
+ out.push({ key: 'Q', data: [...data] });
3296
+ cx = data[2];
3297
+ cy = data[3];
3298
+ break;
3299
+ case 'q': {
3300
+ const newdata = data.map((d, i) => (i % 2) ? (d + cy) : (d + cx));
3301
+ out.push({ key: 'Q', data: newdata });
3302
+ cx = newdata[2];
3303
+ cy = newdata[3];
3304
+ break;
3305
+ }
3306
+ case 'A':
3307
+ out.push({ key: 'A', data: [...data] });
3308
+ cx = data[5];
3309
+ cy = data[6];
3310
+ break;
3311
+ case 'a':
3312
+ cx += data[5];
3313
+ cy += data[6];
3314
+ out.push({ key: 'A', data: [data[0], data[1], data[2], data[3], data[4], cx, cy] });
3315
+ break;
3316
+ case 'H':
3317
+ out.push({ key: 'H', data: [...data] });
3318
+ cx = data[0];
3319
+ break;
3320
+ case 'h':
3321
+ cx += data[0];
3322
+ out.push({ key: 'H', data: [cx] });
3323
+ break;
3324
+ case 'V':
3325
+ out.push({ key: 'V', data: [...data] });
3326
+ cy = data[0];
3327
+ break;
3328
+ case 'v':
3329
+ cy += data[0];
3330
+ out.push({ key: 'V', data: [cy] });
3331
+ break;
3332
+ case 'S':
3333
+ out.push({ key: 'S', data: [...data] });
3334
+ cx = data[2];
3335
+ cy = data[3];
3336
+ break;
3337
+ case 's': {
3338
+ const newdata = data.map((d, i) => (i % 2) ? (d + cy) : (d + cx));
3339
+ out.push({ key: 'S', data: newdata });
3340
+ cx = newdata[2];
3341
+ cy = newdata[3];
3342
+ break;
3343
+ }
3344
+ case 'T':
3345
+ out.push({ key: 'T', data: [...data] });
3346
+ cx = data[0];
3347
+ cy = data[1];
3348
+ break;
3349
+ case 't':
3350
+ cx += data[0];
3351
+ cy += data[1];
3352
+ out.push({ key: 'T', data: [cx, cy] });
3353
+ break;
3354
+ case 'Z':
3355
+ case 'z':
3356
+ out.push({ key: 'Z', data: [] });
3357
+ cx = subx;
3358
+ cy = suby;
3359
+ break;
3360
+ }
3361
+ }
3362
+ return out;
3363
+ }
3364
+
3365
+ // Normalize path to include only M, L, C, and Z commands
3366
+ function normalize(segments) {
3367
+ const out = [];
3368
+ let lastType = '';
3369
+ let cx = 0, cy = 0;
3370
+ let subx = 0, suby = 0;
3371
+ let lcx = 0, lcy = 0;
3372
+ for (const { key, data } of segments) {
3373
+ switch (key) {
3374
+ case 'M':
3375
+ out.push({ key: 'M', data: [...data] });
3376
+ [cx, cy] = data;
3377
+ [subx, suby] = data;
3378
+ break;
3379
+ case 'C':
3380
+ out.push({ key: 'C', data: [...data] });
3381
+ cx = data[4];
3382
+ cy = data[5];
3383
+ lcx = data[2];
3384
+ lcy = data[3];
3385
+ break;
3386
+ case 'L':
3387
+ out.push({ key: 'L', data: [...data] });
3388
+ [cx, cy] = data;
3389
+ break;
3390
+ case 'H':
3391
+ cx = data[0];
3392
+ out.push({ key: 'L', data: [cx, cy] });
3393
+ break;
3394
+ case 'V':
3395
+ cy = data[0];
3396
+ out.push({ key: 'L', data: [cx, cy] });
3397
+ break;
3398
+ case 'S': {
3399
+ let cx1 = 0, cy1 = 0;
3400
+ if (lastType === 'C' || lastType === 'S') {
3401
+ cx1 = cx + (cx - lcx);
3402
+ cy1 = cy + (cy - lcy);
3403
+ }
3404
+ else {
3405
+ cx1 = cx;
3406
+ cy1 = cy;
3407
+ }
3408
+ out.push({ key: 'C', data: [cx1, cy1, ...data] });
3409
+ lcx = data[0];
3410
+ lcy = data[1];
3411
+ cx = data[2];
3412
+ cy = data[3];
3413
+ break;
3414
+ }
3415
+ case 'T': {
3416
+ const [x, y] = data;
3417
+ let x1 = 0, y1 = 0;
3418
+ if (lastType === 'Q' || lastType === 'T') {
3419
+ x1 = cx + (cx - lcx);
3420
+ y1 = cy + (cy - lcy);
3421
+ }
3422
+ else {
3423
+ x1 = cx;
3424
+ y1 = cy;
3425
+ }
3426
+ const cx1 = cx + 2 * (x1 - cx) / 3;
3427
+ const cy1 = cy + 2 * (y1 - cy) / 3;
3428
+ const cx2 = x + 2 * (x1 - x) / 3;
3429
+ const cy2 = y + 2 * (y1 - y) / 3;
3430
+ out.push({ key: 'C', data: [cx1, cy1, cx2, cy2, x, y] });
3431
+ lcx = x1;
3432
+ lcy = y1;
3433
+ cx = x;
3434
+ cy = y;
3435
+ break;
3436
+ }
3437
+ case 'Q': {
3438
+ const [x1, y1, x, y] = data;
3439
+ const cx1 = cx + 2 * (x1 - cx) / 3;
3440
+ const cy1 = cy + 2 * (y1 - cy) / 3;
3441
+ const cx2 = x + 2 * (x1 - x) / 3;
3442
+ const cy2 = y + 2 * (y1 - y) / 3;
3443
+ out.push({ key: 'C', data: [cx1, cy1, cx2, cy2, x, y] });
3444
+ lcx = x1;
3445
+ lcy = y1;
3446
+ cx = x;
3447
+ cy = y;
3448
+ break;
3449
+ }
3450
+ case 'A': {
3451
+ const r1 = Math.abs(data[0]);
3452
+ const r2 = Math.abs(data[1]);
3453
+ const angle = data[2];
3454
+ const largeArcFlag = data[3];
3455
+ const sweepFlag = data[4];
3456
+ const x = data[5];
3457
+ const y = data[6];
3458
+ if (r1 === 0 || r2 === 0) {
3459
+ out.push({ key: 'C', data: [cx, cy, x, y, x, y] });
3460
+ cx = x;
3461
+ cy = y;
3462
+ }
3463
+ else {
3464
+ if (cx !== x || cy !== y) {
3465
+ const curves = arcToCubicCurves(cx, cy, x, y, r1, r2, angle, largeArcFlag, sweepFlag);
3466
+ curves.forEach(function (curve) {
3467
+ out.push({ key: 'C', data: curve });
3468
+ });
3469
+ cx = x;
3470
+ cy = y;
3471
+ }
3472
+ }
3473
+ break;
3474
+ }
3475
+ case 'Z':
3476
+ out.push({ key: 'Z', data: [] });
3477
+ cx = subx;
3478
+ cy = suby;
3479
+ break;
3480
+ }
3481
+ lastType = key;
3482
+ }
3483
+ return out;
3484
+ }
3485
+ function degToRad(degrees) {
3486
+ return (Math.PI * degrees) / 180;
3487
+ }
3488
+ function rotate(x, y, angleRad) {
3489
+ const X = x * Math.cos(angleRad) - y * Math.sin(angleRad);
3490
+ const Y = x * Math.sin(angleRad) + y * Math.cos(angleRad);
3491
+ return [X, Y];
3492
+ }
3493
+ function arcToCubicCurves(x1, y1, x2, y2, r1, r2, angle, largeArcFlag, sweepFlag, recursive) {
3494
+ const angleRad = degToRad(angle);
3495
+ let params = [];
3496
+ let f1 = 0, f2 = 0, cx = 0, cy = 0;
3497
+ if (recursive) {
3498
+ [f1, f2, cx, cy] = recursive;
3499
+ }
3500
+ else {
3501
+ [x1, y1] = rotate(x1, y1, -angleRad);
3502
+ [x2, y2] = rotate(x2, y2, -angleRad);
3503
+ const x = (x1 - x2) / 2;
3504
+ const y = (y1 - y2) / 2;
3505
+ let h = (x * x) / (r1 * r1) + (y * y) / (r2 * r2);
3506
+ if (h > 1) {
3507
+ h = Math.sqrt(h);
3508
+ r1 = h * r1;
3509
+ r2 = h * r2;
3510
+ }
3511
+ const sign = (largeArcFlag === sweepFlag) ? -1 : 1;
3512
+ const r1Pow = r1 * r1;
3513
+ const r2Pow = r2 * r2;
3514
+ const left = r1Pow * r2Pow - r1Pow * y * y - r2Pow * x * x;
3515
+ const right = r1Pow * y * y + r2Pow * x * x;
3516
+ const k = sign * Math.sqrt(Math.abs(left / right));
3517
+ cx = k * r1 * y / r2 + (x1 + x2) / 2;
3518
+ cy = k * -r2 * x / r1 + (y1 + y2) / 2;
3519
+ f1 = Math.asin(parseFloat(((y1 - cy) / r2).toFixed(9)));
3520
+ f2 = Math.asin(parseFloat(((y2 - cy) / r2).toFixed(9)));
3521
+ if (x1 < cx) {
3522
+ f1 = Math.PI - f1;
3523
+ }
3524
+ if (x2 < cx) {
3525
+ f2 = Math.PI - f2;
3526
+ }
3527
+ if (f1 < 0) {
3528
+ f1 = Math.PI * 2 + f1;
3529
+ }
3530
+ if (f2 < 0) {
3531
+ f2 = Math.PI * 2 + f2;
3532
+ }
3533
+ if (sweepFlag && f1 > f2) {
3534
+ f1 = f1 - Math.PI * 2;
3535
+ }
3536
+ if (!sweepFlag && f2 > f1) {
3537
+ f2 = f2 - Math.PI * 2;
3538
+ }
3539
+ }
3540
+ let df = f2 - f1;
3541
+ if (Math.abs(df) > (Math.PI * 120 / 180)) {
3542
+ const f2old = f2;
3543
+ const x2old = x2;
3544
+ const y2old = y2;
3545
+ if (sweepFlag && f2 > f1) {
3546
+ f2 = f1 + (Math.PI * 120 / 180) * (1);
3547
+ }
3548
+ else {
3549
+ f2 = f1 + (Math.PI * 120 / 180) * (-1);
3550
+ }
3551
+ x2 = cx + r1 * Math.cos(f2);
3552
+ y2 = cy + r2 * Math.sin(f2);
3553
+ params = arcToCubicCurves(x2, y2, x2old, y2old, r1, r2, angle, 0, sweepFlag, [f2, f2old, cx, cy]);
3554
+ }
3555
+ df = f2 - f1;
3556
+ const c1 = Math.cos(f1);
3557
+ const s1 = Math.sin(f1);
3558
+ const c2 = Math.cos(f2);
3559
+ const s2 = Math.sin(f2);
3560
+ const t = Math.tan(df / 4);
3561
+ const hx = 4 / 3 * r1 * t;
3562
+ const hy = 4 / 3 * r2 * t;
3563
+ const m1 = [x1, y1];
3564
+ const m2 = [x1 + hx * s1, y1 - hy * c1];
3565
+ const m3 = [x2 + hx * s2, y2 - hy * c2];
3566
+ const m4 = [x2, y2];
3567
+ m2[0] = 2 * m1[0] - m2[0];
3568
+ m2[1] = 2 * m1[1] - m2[1];
3569
+ if (recursive) {
3570
+ return [m2, m3, m4].concat(params);
3571
+ }
3572
+ else {
3573
+ params = [m2, m3, m4].concat(params);
3574
+ const curves = [];
3575
+ for (let i = 0; i < params.length; i += 3) {
3576
+ const r1 = rotate(params[i][0], params[i][1], angleRad);
3577
+ const r2 = rotate(params[i + 1][0], params[i + 1][1], angleRad);
3578
+ const r3 = rotate(params[i + 2][0], params[i + 2][1], angleRad);
3579
+ curves.push([r1[0], r1[1], r2[0], r2[1], r3[0], r3[1]]);
3580
+ }
3581
+ return curves;
3582
+ }
3583
+ }
3584
+
3585
+ const helper = {
3586
+ randOffset,
3587
+ randOffsetWithRange,
3588
+ ellipse,
3589
+ doubleLineOps: doubleLineFillOps,
3590
+ };
3591
+ function line(x1, y1, x2, y2, o) {
3592
+ return { type: 'path', ops: _doubleLine(x1, y1, x2, y2, o) };
3593
+ }
3594
+ function linearPath(points, close, o) {
3595
+ const len = (points || []).length;
3596
+ if (len > 2) {
3597
+ const ops = [];
3598
+ for (let i = 0; i < (len - 1); i++) {
3599
+ ops.push(..._doubleLine(points[i][0], points[i][1], points[i + 1][0], points[i + 1][1], o));
3600
+ }
3601
+ if (close) {
3602
+ ops.push(..._doubleLine(points[len - 1][0], points[len - 1][1], points[0][0], points[0][1], o));
3603
+ }
3604
+ return { type: 'path', ops };
3605
+ }
3606
+ else if (len === 2) {
3607
+ return line(points[0][0], points[0][1], points[1][0], points[1][1], o);
3608
+ }
3609
+ return { type: 'path', ops: [] };
3610
+ }
3611
+ function polygon(points, o) {
3612
+ return linearPath(points, true, o);
3613
+ }
3614
+ function rectangle(x, y, width, height, o) {
3615
+ const points = [
3616
+ [x, y],
3617
+ [x + width, y],
3618
+ [x + width, y + height],
3619
+ [x, y + height],
3620
+ ];
3621
+ return polygon(points, o);
3622
+ }
3623
+ function curve(inputPoints, o) {
3624
+ if (inputPoints.length) {
3625
+ const p1 = inputPoints[0];
3626
+ const pointsList = (typeof p1[0] === 'number') ? [inputPoints] : inputPoints;
3627
+ const o1 = _curveWithOffset(pointsList[0], 1 * (1 + o.roughness * 0.2), o);
3628
+ const o2 = o.disableMultiStroke ? [] : _curveWithOffset(pointsList[0], 1.5 * (1 + o.roughness * 0.22), cloneOptionsAlterSeed(o));
3629
+ for (let i = 1; i < pointsList.length; i++) {
3630
+ const points = pointsList[i];
3631
+ if (points.length) {
3632
+ const underlay = _curveWithOffset(points, 1 * (1 + o.roughness * 0.2), o);
3633
+ const overlay = o.disableMultiStroke ? [] : _curveWithOffset(points, 1.5 * (1 + o.roughness * 0.22), cloneOptionsAlterSeed(o));
3634
+ for (const item of underlay) {
3635
+ if (item.op !== 'move') {
3636
+ o1.push(item);
3637
+ }
3638
+ }
3639
+ for (const item of overlay) {
3640
+ if (item.op !== 'move') {
3641
+ o2.push(item);
3642
+ }
3643
+ }
3644
+ }
3645
+ }
3646
+ return { type: 'path', ops: o1.concat(o2) };
3647
+ }
3648
+ return { type: 'path', ops: [] };
3649
+ }
3650
+ function ellipse(x, y, width, height, o) {
3651
+ const params = generateEllipseParams(width, height, o);
3652
+ return ellipseWithParams(x, y, o, params).opset;
3653
+ }
3654
+ function generateEllipseParams(width, height, o) {
3655
+ const psq = Math.sqrt(Math.PI * 2 * Math.sqrt((Math.pow(width / 2, 2) + Math.pow(height / 2, 2)) / 2));
3656
+ const stepCount = Math.ceil(Math.max(o.curveStepCount, (o.curveStepCount / Math.sqrt(200)) * psq));
3657
+ const increment = (Math.PI * 2) / stepCount;
3658
+ let rx = Math.abs(width / 2);
3659
+ let ry = Math.abs(height / 2);
3660
+ const curveFitRandomness = 1 - o.curveFitting;
3661
+ rx += _offsetOpt(rx * curveFitRandomness, o);
3662
+ ry += _offsetOpt(ry * curveFitRandomness, o);
3663
+ return { increment, rx, ry };
3664
+ }
3665
+ function ellipseWithParams(x, y, o, ellipseParams) {
3666
+ const [ap1, cp1] = _computeEllipsePoints(ellipseParams.increment, x, y, ellipseParams.rx, ellipseParams.ry, 1, ellipseParams.increment * _offset(0.1, _offset(0.4, 1, o), o), o);
3667
+ let o1 = _curve(ap1, null, o);
3668
+ if ((!o.disableMultiStroke) && (o.roughness !== 0)) {
3669
+ const [ap2] = _computeEllipsePoints(ellipseParams.increment, x, y, ellipseParams.rx, ellipseParams.ry, 1.5, 0, o);
3670
+ const o2 = _curve(ap2, null, o);
3671
+ o1 = o1.concat(o2);
3672
+ }
3673
+ return {
3674
+ estimatedPoints: cp1,
3675
+ opset: { type: 'path', ops: o1 },
3676
+ };
3677
+ }
3678
+ function arc(x, y, width, height, start, stop, closed, roughClosure, o) {
3679
+ const cx = x;
3680
+ const cy = y;
3681
+ let rx = Math.abs(width / 2);
3682
+ let ry = Math.abs(height / 2);
3683
+ rx += _offsetOpt(rx * 0.01, o);
3684
+ ry += _offsetOpt(ry * 0.01, o);
3685
+ let strt = start;
3686
+ let stp = stop;
3687
+ while (strt < 0) {
3688
+ strt += Math.PI * 2;
3689
+ stp += Math.PI * 2;
3690
+ }
3691
+ if ((stp - strt) > (Math.PI * 2)) {
3692
+ strt = 0;
3693
+ stp = Math.PI * 2;
3694
+ }
3695
+ const ellipseInc = (Math.PI * 2) / o.curveStepCount;
3696
+ const arcInc = Math.min(ellipseInc / 2, (stp - strt) / 2);
3697
+ const ops = _arc(arcInc, cx, cy, rx, ry, strt, stp, 1, o);
3698
+ if (!o.disableMultiStroke) {
3699
+ const o2 = _arc(arcInc, cx, cy, rx, ry, strt, stp, 1.5, o);
3700
+ ops.push(...o2);
3701
+ }
3702
+ if (closed) {
3703
+ if (roughClosure) {
3704
+ ops.push(..._doubleLine(cx, cy, cx + rx * Math.cos(strt), cy + ry * Math.sin(strt), o), ..._doubleLine(cx, cy, cx + rx * Math.cos(stp), cy + ry * Math.sin(stp), o));
3705
+ }
3706
+ else {
3707
+ ops.push({ op: 'lineTo', data: [cx, cy] }, { op: 'lineTo', data: [cx + rx * Math.cos(strt), cy + ry * Math.sin(strt)] });
3708
+ }
3709
+ }
3710
+ return { type: 'path', ops };
3711
+ }
3712
+ function svgPath(path, o) {
3713
+ const segments = normalize(absolutize(parsePath(path)));
3714
+ const ops = [];
3715
+ let first = [0, 0];
3716
+ let current = [0, 0];
3717
+ for (const { key, data } of segments) {
3718
+ switch (key) {
3719
+ case 'M': {
3720
+ current = [data[0], data[1]];
3721
+ first = [data[0], data[1]];
3722
+ break;
3723
+ }
3724
+ case 'L':
3725
+ ops.push(..._doubleLine(current[0], current[1], data[0], data[1], o));
3726
+ current = [data[0], data[1]];
3727
+ break;
3728
+ case 'C': {
3729
+ const [x1, y1, x2, y2, x, y] = data;
3730
+ ops.push(..._bezierTo(x1, y1, x2, y2, x, y, current, o));
3731
+ current = [x, y];
3732
+ break;
3733
+ }
3734
+ case 'Z':
3735
+ ops.push(..._doubleLine(current[0], current[1], first[0], first[1], o));
3736
+ current = [first[0], first[1]];
3737
+ break;
3738
+ }
3739
+ }
3740
+ return { type: 'path', ops };
3741
+ }
3742
+ // Fills
3743
+ function solidFillPolygon(polygonList, o) {
3744
+ const ops = [];
3745
+ for (const points of polygonList) {
3746
+ if (points.length) {
3747
+ const offset = o.maxRandomnessOffset || 0;
3748
+ const len = points.length;
3749
+ if (len > 2) {
3750
+ ops.push({ op: 'move', data: [points[0][0] + _offsetOpt(offset, o), points[0][1] + _offsetOpt(offset, o)] });
3751
+ for (let i = 1; i < len; i++) {
3752
+ ops.push({ op: 'lineTo', data: [points[i][0] + _offsetOpt(offset, o), points[i][1] + _offsetOpt(offset, o)] });
3753
+ }
3754
+ }
3755
+ }
3756
+ }
3757
+ return { type: 'fillPath', ops };
3758
+ }
3759
+ function patternFillPolygons(polygonList, o) {
3760
+ return getFiller(o, helper).fillPolygons(polygonList, o);
3761
+ }
3762
+ function patternFillArc(x, y, width, height, start, stop, o) {
3763
+ const cx = x;
3764
+ const cy = y;
3765
+ let rx = Math.abs(width / 2);
3766
+ let ry = Math.abs(height / 2);
3767
+ rx += _offsetOpt(rx * 0.01, o);
3768
+ ry += _offsetOpt(ry * 0.01, o);
3769
+ let strt = start;
3770
+ let stp = stop;
3771
+ while (strt < 0) {
3772
+ strt += Math.PI * 2;
3773
+ stp += Math.PI * 2;
3774
+ }
3775
+ if ((stp - strt) > (Math.PI * 2)) {
3776
+ strt = 0;
3777
+ stp = Math.PI * 2;
3778
+ }
3779
+ const increment = (stp - strt) / o.curveStepCount;
3780
+ const points = [];
3781
+ for (let angle = strt; angle <= stp; angle = angle + increment) {
3782
+ points.push([cx + rx * Math.cos(angle), cy + ry * Math.sin(angle)]);
3783
+ }
3784
+ points.push([cx + rx * Math.cos(stp), cy + ry * Math.sin(stp)]);
3785
+ points.push([cx, cy]);
3786
+ return patternFillPolygons([points], o);
3787
+ }
3788
+ function randOffset(x, o) {
3789
+ return _offsetOpt(x, o);
3790
+ }
3791
+ function randOffsetWithRange(min, max, o) {
3792
+ return _offset(min, max, o);
3793
+ }
3794
+ function doubleLineFillOps(x1, y1, x2, y2, o) {
3795
+ return _doubleLine(x1, y1, x2, y2, o, true);
3796
+ }
3797
+ // Private helpers
3798
+ function cloneOptionsAlterSeed(ops) {
3799
+ const result = Object.assign({}, ops);
3800
+ result.randomizer = undefined;
3801
+ if (ops.seed) {
3802
+ result.seed = ops.seed + 1;
3803
+ }
3804
+ return result;
3805
+ }
3806
+ function random(ops) {
3807
+ if (!ops.randomizer) {
3808
+ ops.randomizer = new Random(ops.seed || 0);
3809
+ }
3810
+ return ops.randomizer.next();
3811
+ }
3812
+ function _offset(min, max, ops, roughnessGain = 1) {
3813
+ return ops.roughness * roughnessGain * ((random(ops) * (max - min)) + min);
3814
+ }
3815
+ function _offsetOpt(x, ops, roughnessGain = 1) {
3816
+ return _offset(-x, x, ops, roughnessGain);
3817
+ }
3818
+ function _doubleLine(x1, y1, x2, y2, o, filling = false) {
3819
+ const singleStroke = filling ? o.disableMultiStrokeFill : o.disableMultiStroke;
3820
+ const o1 = _line(x1, y1, x2, y2, o, true, false);
3821
+ if (singleStroke) {
3822
+ return o1;
3823
+ }
3824
+ const o2 = _line(x1, y1, x2, y2, o, true, true);
3825
+ return o1.concat(o2);
3826
+ }
3827
+ function _line(x1, y1, x2, y2, o, move, overlay) {
3828
+ const lengthSq = Math.pow((x1 - x2), 2) + Math.pow((y1 - y2), 2);
3829
+ const length = Math.sqrt(lengthSq);
3830
+ let roughnessGain = 1;
3831
+ if (length < 200) {
3832
+ roughnessGain = 1;
3833
+ }
3834
+ else if (length > 500) {
3835
+ roughnessGain = 0.4;
3836
+ }
3837
+ else {
3838
+ roughnessGain = (-16668e-7) * length + 1.233334;
3839
+ }
3840
+ let offset = o.maxRandomnessOffset || 0;
3841
+ if ((offset * offset * 100) > lengthSq) {
3842
+ offset = length / 10;
3843
+ }
3844
+ const halfOffset = offset / 2;
3845
+ const divergePoint = 0.2 + random(o) * 0.2;
3846
+ let midDispX = o.bowing * o.maxRandomnessOffset * (y2 - y1) / 200;
3847
+ let midDispY = o.bowing * o.maxRandomnessOffset * (x1 - x2) / 200;
3848
+ midDispX = _offsetOpt(midDispX, o, roughnessGain);
3849
+ midDispY = _offsetOpt(midDispY, o, roughnessGain);
3850
+ const ops = [];
3851
+ const randomHalf = () => _offsetOpt(halfOffset, o, roughnessGain);
3852
+ const randomFull = () => _offsetOpt(offset, o, roughnessGain);
3853
+ const preserveVertices = o.preserveVertices;
3854
+ {
3855
+ if (overlay) {
3856
+ ops.push({
3857
+ op: 'move', data: [
3858
+ x1 + (preserveVertices ? 0 : randomHalf()),
3859
+ y1 + (preserveVertices ? 0 : randomHalf()),
3860
+ ],
3861
+ });
3862
+ }
3863
+ else {
3864
+ ops.push({
3865
+ op: 'move', data: [
3866
+ x1 + (preserveVertices ? 0 : _offsetOpt(offset, o, roughnessGain)),
3867
+ y1 + (preserveVertices ? 0 : _offsetOpt(offset, o, roughnessGain)),
3868
+ ],
3869
+ });
3870
+ }
3871
+ }
3872
+ if (overlay) {
3873
+ ops.push({
3874
+ op: 'bcurveTo',
3875
+ data: [
3876
+ midDispX + x1 + (x2 - x1) * divergePoint + randomHalf(),
3877
+ midDispY + y1 + (y2 - y1) * divergePoint + randomHalf(),
3878
+ midDispX + x1 + 2 * (x2 - x1) * divergePoint + randomHalf(),
3879
+ midDispY + y1 + 2 * (y2 - y1) * divergePoint + randomHalf(),
3880
+ x2 + (preserveVertices ? 0 : randomHalf()),
3881
+ y2 + (preserveVertices ? 0 : randomHalf()),
3882
+ ],
3883
+ });
3884
+ }
3885
+ else {
3886
+ ops.push({
3887
+ op: 'bcurveTo',
3888
+ data: [
3889
+ midDispX + x1 + (x2 - x1) * divergePoint + randomFull(),
3890
+ midDispY + y1 + (y2 - y1) * divergePoint + randomFull(),
3891
+ midDispX + x1 + 2 * (x2 - x1) * divergePoint + randomFull(),
3892
+ midDispY + y1 + 2 * (y2 - y1) * divergePoint + randomFull(),
3893
+ x2 + (preserveVertices ? 0 : randomFull()),
3894
+ y2 + (preserveVertices ? 0 : randomFull()),
3895
+ ],
3896
+ });
3897
+ }
3898
+ return ops;
3899
+ }
3900
+ function _curveWithOffset(points, offset, o) {
3901
+ if (!points.length) {
3902
+ return [];
3903
+ }
3904
+ const ps = [];
3905
+ ps.push([
3906
+ points[0][0] + _offsetOpt(offset, o),
3907
+ points[0][1] + _offsetOpt(offset, o),
3908
+ ]);
3909
+ ps.push([
3910
+ points[0][0] + _offsetOpt(offset, o),
3911
+ points[0][1] + _offsetOpt(offset, o),
3912
+ ]);
3913
+ for (let i = 1; i < points.length; i++) {
3914
+ ps.push([
3915
+ points[i][0] + _offsetOpt(offset, o),
3916
+ points[i][1] + _offsetOpt(offset, o),
3917
+ ]);
3918
+ if (i === (points.length - 1)) {
3919
+ ps.push([
3920
+ points[i][0] + _offsetOpt(offset, o),
3921
+ points[i][1] + _offsetOpt(offset, o),
3922
+ ]);
3923
+ }
3924
+ }
3925
+ return _curve(ps, null, o);
3926
+ }
3927
+ function _curve(points, closePoint, o) {
3928
+ const len = points.length;
3929
+ const ops = [];
3930
+ if (len > 3) {
3931
+ const b = [];
3932
+ const s = 1 - o.curveTightness;
3933
+ ops.push({ op: 'move', data: [points[1][0], points[1][1]] });
3934
+ for (let i = 1; (i + 2) < len; i++) {
3935
+ const cachedVertArray = points[i];
3936
+ b[0] = [cachedVertArray[0], cachedVertArray[1]];
3937
+ b[1] = [cachedVertArray[0] + (s * points[i + 1][0] - s * points[i - 1][0]) / 6, cachedVertArray[1] + (s * points[i + 1][1] - s * points[i - 1][1]) / 6];
3938
+ b[2] = [points[i + 1][0] + (s * points[i][0] - s * points[i + 2][0]) / 6, points[i + 1][1] + (s * points[i][1] - s * points[i + 2][1]) / 6];
3939
+ b[3] = [points[i + 1][0], points[i + 1][1]];
3940
+ ops.push({ op: 'bcurveTo', data: [b[1][0], b[1][1], b[2][0], b[2][1], b[3][0], b[3][1]] });
3941
+ }
3942
+ }
3943
+ else if (len === 3) {
3944
+ ops.push({ op: 'move', data: [points[1][0], points[1][1]] });
3945
+ ops.push({
3946
+ op: 'bcurveTo',
3947
+ data: [
3948
+ points[1][0], points[1][1],
3949
+ points[2][0], points[2][1],
3950
+ points[2][0], points[2][1],
3951
+ ],
3952
+ });
3953
+ }
3954
+ else if (len === 2) {
3955
+ ops.push(..._line(points[0][0], points[0][1], points[1][0], points[1][1], o, true, true));
3956
+ }
3957
+ return ops;
3958
+ }
3959
+ function _computeEllipsePoints(increment, cx, cy, rx, ry, offset, overlap, o) {
3960
+ const coreOnly = o.roughness === 0;
3961
+ const corePoints = [];
3962
+ const allPoints = [];
3963
+ if (coreOnly) {
3964
+ increment = increment / 4;
3965
+ allPoints.push([
3966
+ cx + rx * Math.cos(-increment),
3967
+ cy + ry * Math.sin(-increment),
3968
+ ]);
3969
+ for (let angle = 0; angle <= Math.PI * 2; angle = angle + increment) {
3970
+ const p = [
3971
+ cx + rx * Math.cos(angle),
3972
+ cy + ry * Math.sin(angle),
3973
+ ];
3974
+ corePoints.push(p);
3975
+ allPoints.push(p);
3976
+ }
3977
+ allPoints.push([
3978
+ cx + rx * Math.cos(0),
3979
+ cy + ry * Math.sin(0),
3980
+ ]);
3981
+ allPoints.push([
3982
+ cx + rx * Math.cos(increment),
3983
+ cy + ry * Math.sin(increment),
3984
+ ]);
3985
+ }
3986
+ else {
3987
+ const radOffset = _offsetOpt(0.5, o) - (Math.PI / 2);
3988
+ allPoints.push([
3989
+ _offsetOpt(offset, o) + cx + 0.9 * rx * Math.cos(radOffset - increment),
3990
+ _offsetOpt(offset, o) + cy + 0.9 * ry * Math.sin(radOffset - increment),
3991
+ ]);
3992
+ const endAngle = Math.PI * 2 + radOffset - 0.01;
3993
+ for (let angle = radOffset; angle < endAngle; angle = angle + increment) {
3994
+ const p = [
3995
+ _offsetOpt(offset, o) + cx + rx * Math.cos(angle),
3996
+ _offsetOpt(offset, o) + cy + ry * Math.sin(angle),
3997
+ ];
3998
+ corePoints.push(p);
3999
+ allPoints.push(p);
4000
+ }
4001
+ allPoints.push([
4002
+ _offsetOpt(offset, o) + cx + rx * Math.cos(radOffset + Math.PI * 2 + overlap * 0.5),
4003
+ _offsetOpt(offset, o) + cy + ry * Math.sin(radOffset + Math.PI * 2 + overlap * 0.5),
4004
+ ]);
4005
+ allPoints.push([
4006
+ _offsetOpt(offset, o) + cx + 0.98 * rx * Math.cos(radOffset + overlap),
4007
+ _offsetOpt(offset, o) + cy + 0.98 * ry * Math.sin(radOffset + overlap),
4008
+ ]);
4009
+ allPoints.push([
4010
+ _offsetOpt(offset, o) + cx + 0.9 * rx * Math.cos(radOffset + overlap * 0.5),
4011
+ _offsetOpt(offset, o) + cy + 0.9 * ry * Math.sin(radOffset + overlap * 0.5),
4012
+ ]);
4013
+ }
4014
+ return [allPoints, corePoints];
4015
+ }
4016
+ function _arc(increment, cx, cy, rx, ry, strt, stp, offset, o) {
4017
+ const radOffset = strt + _offsetOpt(0.1, o);
4018
+ const points = [];
4019
+ points.push([
4020
+ _offsetOpt(offset, o) + cx + 0.9 * rx * Math.cos(radOffset - increment),
4021
+ _offsetOpt(offset, o) + cy + 0.9 * ry * Math.sin(radOffset - increment),
4022
+ ]);
4023
+ for (let angle = radOffset; angle <= stp; angle = angle + increment) {
4024
+ points.push([
4025
+ _offsetOpt(offset, o) + cx + rx * Math.cos(angle),
4026
+ _offsetOpt(offset, o) + cy + ry * Math.sin(angle),
4027
+ ]);
4028
+ }
4029
+ points.push([
4030
+ cx + rx * Math.cos(stp),
4031
+ cy + ry * Math.sin(stp),
4032
+ ]);
4033
+ points.push([
4034
+ cx + rx * Math.cos(stp),
4035
+ cy + ry * Math.sin(stp),
4036
+ ]);
4037
+ return _curve(points, null, o);
4038
+ }
4039
+ function _bezierTo(x1, y1, x2, y2, x, y, current, o) {
4040
+ const ops = [];
4041
+ const ros = [o.maxRandomnessOffset || 1, (o.maxRandomnessOffset || 1) + 0.3];
4042
+ let f = [0, 0];
4043
+ const iterations = o.disableMultiStroke ? 1 : 2;
4044
+ const preserveVertices = o.preserveVertices;
4045
+ for (let i = 0; i < iterations; i++) {
4046
+ if (i === 0) {
4047
+ ops.push({ op: 'move', data: [current[0], current[1]] });
4048
+ }
4049
+ else {
4050
+ ops.push({ op: 'move', data: [current[0] + (preserveVertices ? 0 : _offsetOpt(ros[0], o)), current[1] + (preserveVertices ? 0 : _offsetOpt(ros[0], o))] });
4051
+ }
4052
+ f = preserveVertices ? [x, y] : [x + _offsetOpt(ros[i], o), y + _offsetOpt(ros[i], o)];
4053
+ ops.push({
4054
+ op: 'bcurveTo',
4055
+ data: [
4056
+ x1 + _offsetOpt(ros[i], o), y1 + _offsetOpt(ros[i], o),
4057
+ x2 + _offsetOpt(ros[i], o), y2 + _offsetOpt(ros[i], o),
4058
+ f[0], f[1],
4059
+ ],
4060
+ });
4061
+ }
4062
+ return ops;
4063
+ }
4064
+
4065
+ function clone(p) {
4066
+ return [...p];
4067
+ }
4068
+ function curveToBezier(pointsIn, curveTightness = 0) {
4069
+ const len = pointsIn.length;
4070
+ if (len < 3) {
4071
+ throw new Error('A curve must have at least three points.');
4072
+ }
4073
+ const out = [];
4074
+ if (len === 3) {
4075
+ out.push(clone(pointsIn[0]), clone(pointsIn[1]), clone(pointsIn[2]), clone(pointsIn[2]));
4076
+ }
4077
+ else {
4078
+ const points = [];
4079
+ points.push(pointsIn[0], pointsIn[0]);
4080
+ for (let i = 1; i < pointsIn.length; i++) {
4081
+ points.push(pointsIn[i]);
4082
+ if (i === (pointsIn.length - 1)) {
4083
+ points.push(pointsIn[i]);
4084
+ }
4085
+ }
4086
+ const b = [];
4087
+ const s = 1 - curveTightness;
4088
+ out.push(clone(points[0]));
4089
+ for (let i = 1; (i + 2) < points.length; i++) {
4090
+ const cachedVertArray = points[i];
4091
+ b[0] = [cachedVertArray[0], cachedVertArray[1]];
4092
+ b[1] = [cachedVertArray[0] + (s * points[i + 1][0] - s * points[i - 1][0]) / 6, cachedVertArray[1] + (s * points[i + 1][1] - s * points[i - 1][1]) / 6];
4093
+ b[2] = [points[i + 1][0] + (s * points[i][0] - s * points[i + 2][0]) / 6, points[i + 1][1] + (s * points[i][1] - s * points[i + 2][1]) / 6];
4094
+ b[3] = [points[i + 1][0], points[i + 1][1]];
4095
+ out.push(b[1], b[2], b[3]);
4096
+ }
4097
+ }
4098
+ return out;
4099
+ }
4100
+
4101
+ // distance between 2 points
4102
+ function distance(p1, p2) {
4103
+ return Math.sqrt(distanceSq(p1, p2));
4104
+ }
4105
+ // distance between 2 points squared
4106
+ function distanceSq(p1, p2) {
4107
+ return Math.pow(p1[0] - p2[0], 2) + Math.pow(p1[1] - p2[1], 2);
4108
+ }
4109
+ // Sistance squared from a point p to the line segment vw
4110
+ function distanceToSegmentSq(p, v, w) {
4111
+ const l2 = distanceSq(v, w);
4112
+ if (l2 === 0) {
4113
+ return distanceSq(p, v);
4114
+ }
4115
+ let t = ((p[0] - v[0]) * (w[0] - v[0]) + (p[1] - v[1]) * (w[1] - v[1])) / l2;
4116
+ t = Math.max(0, Math.min(1, t));
4117
+ return distanceSq(p, lerp$1(v, w, t));
4118
+ }
4119
+ function lerp$1(a, b, t) {
4120
+ return [
4121
+ a[0] + (b[0] - a[0]) * t,
4122
+ a[1] + (b[1] - a[1]) * t,
4123
+ ];
4124
+ }
4125
+ // Adapted from https://seant23.wordpress.com/2010/11/12/offset-bezier-curves/
4126
+ function flatness(points, offset) {
4127
+ const p1 = points[offset + 0];
4128
+ const p2 = points[offset + 1];
4129
+ const p3 = points[offset + 2];
4130
+ const p4 = points[offset + 3];
4131
+ let ux = 3 * p2[0] - 2 * p1[0] - p4[0];
4132
+ ux *= ux;
4133
+ let uy = 3 * p2[1] - 2 * p1[1] - p4[1];
4134
+ uy *= uy;
4135
+ let vx = 3 * p3[0] - 2 * p4[0] - p1[0];
4136
+ vx *= vx;
4137
+ let vy = 3 * p3[1] - 2 * p4[1] - p1[1];
4138
+ vy *= vy;
4139
+ if (ux < vx) {
4140
+ ux = vx;
4141
+ }
4142
+ if (uy < vy) {
4143
+ uy = vy;
4144
+ }
4145
+ return ux + uy;
4146
+ }
4147
+ function getPointsOnBezierCurveWithSplitting(points, offset, tolerance, newPoints) {
4148
+ const outPoints = newPoints || [];
4149
+ if (flatness(points, offset) < tolerance) {
4150
+ const p0 = points[offset + 0];
4151
+ if (outPoints.length) {
4152
+ const d = distance(outPoints[outPoints.length - 1], p0);
4153
+ if (d > 1) {
4154
+ outPoints.push(p0);
4155
+ }
4156
+ }
4157
+ else {
4158
+ outPoints.push(p0);
4159
+ }
4160
+ outPoints.push(points[offset + 3]);
4161
+ }
4162
+ else {
4163
+ // subdivide
4164
+ const t = .5;
4165
+ const p1 = points[offset + 0];
4166
+ const p2 = points[offset + 1];
4167
+ const p3 = points[offset + 2];
4168
+ const p4 = points[offset + 3];
4169
+ const q1 = lerp$1(p1, p2, t);
4170
+ const q2 = lerp$1(p2, p3, t);
4171
+ const q3 = lerp$1(p3, p4, t);
4172
+ const r1 = lerp$1(q1, q2, t);
4173
+ const r2 = lerp$1(q2, q3, t);
4174
+ const red = lerp$1(r1, r2, t);
4175
+ getPointsOnBezierCurveWithSplitting([p1, q1, r1, red], 0, tolerance, outPoints);
4176
+ getPointsOnBezierCurveWithSplitting([red, r2, q3, p4], 0, tolerance, outPoints);
4177
+ }
4178
+ return outPoints;
4179
+ }
4180
+ function simplify(points, distance) {
4181
+ return simplifyPoints(points, 0, points.length, distance);
4182
+ }
4183
+ // Ramer–Douglas–Peucker algorithm
4184
+ // https://en.wikipedia.org/wiki/Ramer%E2%80%93Douglas%E2%80%93Peucker_algorithm
4185
+ function simplifyPoints(points, start, end, epsilon, newPoints) {
4186
+ const outPoints = newPoints || [];
4187
+ // find the most distance point from the endpoints
4188
+ const s = points[start];
4189
+ const e = points[end - 1];
4190
+ let maxDistSq = 0;
4191
+ let maxNdx = 1;
4192
+ for (let i = start + 1; i < end - 1; ++i) {
4193
+ const distSq = distanceToSegmentSq(points[i], s, e);
4194
+ if (distSq > maxDistSq) {
4195
+ maxDistSq = distSq;
4196
+ maxNdx = i;
4197
+ }
4198
+ }
4199
+ // if that point is too far, split
4200
+ if (Math.sqrt(maxDistSq) > epsilon) {
4201
+ simplifyPoints(points, start, maxNdx + 1, epsilon, outPoints);
4202
+ simplifyPoints(points, maxNdx, end, epsilon, outPoints);
4203
+ }
4204
+ else {
4205
+ if (!outPoints.length) {
4206
+ outPoints.push(s);
4207
+ }
4208
+ outPoints.push(e);
4209
+ }
4210
+ return outPoints;
4211
+ }
4212
+ function pointsOnBezierCurves(points, tolerance = 0.15, distance) {
4213
+ const newPoints = [];
4214
+ const numSegments = (points.length - 1) / 3;
4215
+ for (let i = 0; i < numSegments; i++) {
4216
+ const offset = i * 3;
4217
+ getPointsOnBezierCurveWithSplitting(points, offset, tolerance, newPoints);
4218
+ }
4219
+ if (distance && distance > 0) {
4220
+ return simplifyPoints(newPoints, 0, newPoints.length, distance);
4221
+ }
4222
+ return newPoints;
4223
+ }
4224
+
4225
+ function pointsOnPath(path, tolerance, distance) {
4226
+ const segments = parsePath(path);
4227
+ const normalized = normalize(absolutize(segments));
4228
+ const sets = [];
4229
+ let currentPoints = [];
4230
+ let start = [0, 0];
4231
+ let pendingCurve = [];
4232
+ const appendPendingCurve = () => {
4233
+ if (pendingCurve.length >= 4) {
4234
+ currentPoints.push(...pointsOnBezierCurves(pendingCurve, tolerance));
4235
+ }
4236
+ pendingCurve = [];
4237
+ };
4238
+ const appendPendingPoints = () => {
4239
+ appendPendingCurve();
4240
+ if (currentPoints.length) {
4241
+ sets.push(currentPoints);
4242
+ currentPoints = [];
4243
+ }
4244
+ };
4245
+ for (const { key, data } of normalized) {
4246
+ switch (key) {
4247
+ case 'M':
4248
+ appendPendingPoints();
4249
+ start = [data[0], data[1]];
4250
+ currentPoints.push(start);
4251
+ break;
4252
+ case 'L':
4253
+ appendPendingCurve();
4254
+ currentPoints.push([data[0], data[1]]);
4255
+ break;
4256
+ case 'C':
4257
+ if (!pendingCurve.length) {
4258
+ const lastPoint = currentPoints.length ? currentPoints[currentPoints.length - 1] : start;
4259
+ pendingCurve.push([lastPoint[0], lastPoint[1]]);
4260
+ }
4261
+ pendingCurve.push([data[0], data[1]]);
4262
+ pendingCurve.push([data[2], data[3]]);
4263
+ pendingCurve.push([data[4], data[5]]);
4264
+ break;
4265
+ case 'Z':
4266
+ appendPendingCurve();
4267
+ currentPoints.push([start[0], start[1]]);
4268
+ break;
4269
+ }
4270
+ }
4271
+ appendPendingPoints();
4272
+ if (!distance) {
4273
+ return sets;
4274
+ }
4275
+ const out = [];
4276
+ for (const set of sets) {
4277
+ const simplifiedSet = simplify(set, distance);
4278
+ if (simplifiedSet.length) {
4279
+ out.push(simplifiedSet);
4280
+ }
4281
+ }
4282
+ return out;
4283
+ }
4284
+
4285
+ const NOS = 'none';
4286
+ class RoughGenerator {
4287
+ constructor(config) {
4288
+ this.defaultOptions = {
4289
+ maxRandomnessOffset: 2,
4290
+ roughness: 1,
4291
+ bowing: 1,
4292
+ stroke: '#000',
4293
+ strokeWidth: 1,
4294
+ curveTightness: 0,
4295
+ curveFitting: 0.95,
4296
+ curveStepCount: 9,
4297
+ fillStyle: 'hachure',
4298
+ fillWeight: -1,
4299
+ hachureAngle: -41,
4300
+ hachureGap: -1,
4301
+ dashOffset: -1,
4302
+ dashGap: -1,
4303
+ zigzagOffset: -1,
4304
+ seed: 0,
4305
+ disableMultiStroke: false,
4306
+ disableMultiStrokeFill: false,
4307
+ preserveVertices: false,
4308
+ fillShapeRoughnessGain: 0.8,
4309
+ };
4310
+ this.config = config || {};
4311
+ if (this.config.options) {
4312
+ this.defaultOptions = this._o(this.config.options);
4313
+ }
4314
+ }
4315
+ static newSeed() {
4316
+ return randomSeed();
4317
+ }
4318
+ _o(options) {
4319
+ return options ? Object.assign({}, this.defaultOptions, options) : this.defaultOptions;
4320
+ }
4321
+ _d(shape, sets, options) {
4322
+ return { shape, sets: sets || [], options: options || this.defaultOptions };
4323
+ }
4324
+ line(x1, y1, x2, y2, options) {
4325
+ const o = this._o(options);
4326
+ return this._d('line', [line(x1, y1, x2, y2, o)], o);
4327
+ }
4328
+ rectangle(x, y, width, height, options) {
4329
+ const o = this._o(options);
4330
+ const paths = [];
4331
+ const outline = rectangle(x, y, width, height, o);
4332
+ if (o.fill) {
4333
+ const points = [[x, y], [x + width, y], [x + width, y + height], [x, y + height]];
4334
+ if (o.fillStyle === 'solid') {
4335
+ paths.push(solidFillPolygon([points], o));
4336
+ }
4337
+ else {
4338
+ paths.push(patternFillPolygons([points], o));
4339
+ }
4340
+ }
4341
+ if (o.stroke !== NOS) {
4342
+ paths.push(outline);
4343
+ }
4344
+ return this._d('rectangle', paths, o);
4345
+ }
4346
+ ellipse(x, y, width, height, options) {
4347
+ const o = this._o(options);
4348
+ const paths = [];
4349
+ const ellipseParams = generateEllipseParams(width, height, o);
4350
+ const ellipseResponse = ellipseWithParams(x, y, o, ellipseParams);
4351
+ if (o.fill) {
4352
+ if (o.fillStyle === 'solid') {
4353
+ const shape = ellipseWithParams(x, y, o, ellipseParams).opset;
4354
+ shape.type = 'fillPath';
4355
+ paths.push(shape);
4356
+ }
4357
+ else {
4358
+ paths.push(patternFillPolygons([ellipseResponse.estimatedPoints], o));
4359
+ }
4360
+ }
4361
+ if (o.stroke !== NOS) {
4362
+ paths.push(ellipseResponse.opset);
4363
+ }
4364
+ return this._d('ellipse', paths, o);
4365
+ }
4366
+ circle(x, y, diameter, options) {
4367
+ const ret = this.ellipse(x, y, diameter, diameter, options);
4368
+ ret.shape = 'circle';
4369
+ return ret;
4370
+ }
4371
+ linearPath(points, options) {
4372
+ const o = this._o(options);
4373
+ return this._d('linearPath', [linearPath(points, false, o)], o);
4374
+ }
4375
+ arc(x, y, width, height, start, stop, closed = false, options) {
4376
+ const o = this._o(options);
4377
+ const paths = [];
4378
+ const outline = arc(x, y, width, height, start, stop, closed, true, o);
4379
+ if (closed && o.fill) {
4380
+ if (o.fillStyle === 'solid') {
4381
+ const fillOptions = Object.assign({}, o);
4382
+ fillOptions.disableMultiStroke = true;
4383
+ const shape = arc(x, y, width, height, start, stop, true, false, fillOptions);
4384
+ shape.type = 'fillPath';
4385
+ paths.push(shape);
4386
+ }
4387
+ else {
4388
+ paths.push(patternFillArc(x, y, width, height, start, stop, o));
4389
+ }
4390
+ }
4391
+ if (o.stroke !== NOS) {
4392
+ paths.push(outline);
4393
+ }
4394
+ return this._d('arc', paths, o);
4395
+ }
4396
+ curve(points, options) {
4397
+ const o = this._o(options);
4398
+ const paths = [];
4399
+ const outline = curve(points, o);
4400
+ if (o.fill && o.fill !== NOS) {
4401
+ if (o.fillStyle === 'solid') {
4402
+ const fillShape = curve(points, Object.assign(Object.assign({}, o), { disableMultiStroke: true, roughness: o.roughness ? (o.roughness + o.fillShapeRoughnessGain) : 0 }));
4403
+ paths.push({
4404
+ type: 'fillPath',
4405
+ ops: this._mergedShape(fillShape.ops),
4406
+ });
4407
+ }
4408
+ else {
4409
+ const polyPoints = [];
4410
+ const inputPoints = points;
4411
+ if (inputPoints.length) {
4412
+ const p1 = inputPoints[0];
4413
+ const pointsList = (typeof p1[0] === 'number') ? [inputPoints] : inputPoints;
4414
+ for (const points of pointsList) {
4415
+ if (points.length < 3) {
4416
+ polyPoints.push(...points);
4417
+ }
4418
+ else if (points.length === 3) {
4419
+ polyPoints.push(...pointsOnBezierCurves(curveToBezier([
4420
+ points[0],
4421
+ points[0],
4422
+ points[1],
4423
+ points[2],
4424
+ ]), 10, (1 + o.roughness) / 2));
4425
+ }
4426
+ else {
4427
+ polyPoints.push(...pointsOnBezierCurves(curveToBezier(points), 10, (1 + o.roughness) / 2));
4428
+ }
4429
+ }
4430
+ }
4431
+ if (polyPoints.length) {
4432
+ paths.push(patternFillPolygons([polyPoints], o));
4433
+ }
4434
+ }
4435
+ }
4436
+ if (o.stroke !== NOS) {
4437
+ paths.push(outline);
4438
+ }
4439
+ return this._d('curve', paths, o);
4440
+ }
4441
+ polygon(points, options) {
4442
+ const o = this._o(options);
4443
+ const paths = [];
4444
+ const outline = linearPath(points, true, o);
4445
+ if (o.fill) {
4446
+ if (o.fillStyle === 'solid') {
4447
+ paths.push(solidFillPolygon([points], o));
4448
+ }
4449
+ else {
4450
+ paths.push(patternFillPolygons([points], o));
4451
+ }
4452
+ }
4453
+ if (o.stroke !== NOS) {
4454
+ paths.push(outline);
4455
+ }
4456
+ return this._d('polygon', paths, o);
4457
+ }
4458
+ path(d, options) {
4459
+ const o = this._o(options);
4460
+ const paths = [];
4461
+ if (!d) {
4462
+ return this._d('path', paths, o);
4463
+ }
4464
+ d = (d || '').replace(/\n/g, ' ').replace(/(-\s)/g, '-').replace('/(\s\s)/g', ' ');
4465
+ const hasFill = o.fill && o.fill !== 'transparent' && o.fill !== NOS;
4466
+ const hasStroke = o.stroke !== NOS;
4467
+ const simplified = !!(o.simplification && (o.simplification < 1));
4468
+ const distance = simplified ? (4 - 4 * (o.simplification || 1)) : ((1 + o.roughness) / 2);
4469
+ const sets = pointsOnPath(d, 1, distance);
4470
+ const shape = svgPath(d, o);
4471
+ if (hasFill) {
4472
+ if (o.fillStyle === 'solid') {
4473
+ if (sets.length === 1) {
4474
+ const fillShape = svgPath(d, Object.assign(Object.assign({}, o), { disableMultiStroke: true, roughness: o.roughness ? (o.roughness + o.fillShapeRoughnessGain) : 0 }));
4475
+ paths.push({
4476
+ type: 'fillPath',
4477
+ ops: this._mergedShape(fillShape.ops),
4478
+ });
4479
+ }
4480
+ else {
4481
+ paths.push(solidFillPolygon(sets, o));
4482
+ }
4483
+ }
4484
+ else {
4485
+ paths.push(patternFillPolygons(sets, o));
4486
+ }
4487
+ }
4488
+ if (hasStroke) {
4489
+ if (simplified) {
4490
+ sets.forEach((set) => {
4491
+ paths.push(linearPath(set, false, o));
4492
+ });
4493
+ }
4494
+ else {
4495
+ paths.push(shape);
4496
+ }
4497
+ }
4498
+ return this._d('path', paths, o);
4499
+ }
4500
+ opsToPath(drawing, fixedDecimals) {
4501
+ let path = '';
4502
+ for (const item of drawing.ops) {
4503
+ const data = ((typeof fixedDecimals === 'number') && fixedDecimals >= 0) ? (item.data.map((d) => +d.toFixed(fixedDecimals))) : item.data;
4504
+ switch (item.op) {
4505
+ case 'move':
4506
+ path += `M${data[0]} ${data[1]} `;
4507
+ break;
4508
+ case 'bcurveTo':
4509
+ path += `C${data[0]} ${data[1]}, ${data[2]} ${data[3]}, ${data[4]} ${data[5]} `;
4510
+ break;
4511
+ case 'lineTo':
4512
+ path += `L${data[0]} ${data[1]} `;
4513
+ break;
4514
+ }
4515
+ }
4516
+ return path.trim();
4517
+ }
4518
+ toPaths(drawable) {
4519
+ const sets = drawable.sets || [];
4520
+ const o = drawable.options || this.defaultOptions;
4521
+ const paths = [];
4522
+ for (const drawing of sets) {
4523
+ let path = null;
4524
+ switch (drawing.type) {
4525
+ case 'path':
4526
+ path = {
4527
+ d: this.opsToPath(drawing),
4528
+ stroke: o.stroke,
4529
+ strokeWidth: o.strokeWidth,
4530
+ fill: NOS,
4531
+ };
4532
+ break;
4533
+ case 'fillPath':
4534
+ path = {
4535
+ d: this.opsToPath(drawing),
4536
+ stroke: NOS,
4537
+ strokeWidth: 0,
4538
+ fill: o.fill || NOS,
4539
+ };
4540
+ break;
4541
+ case 'fillSketch':
4542
+ path = this.fillSketch(drawing, o);
4543
+ break;
4544
+ }
4545
+ if (path) {
4546
+ paths.push(path);
4547
+ }
4548
+ }
4549
+ return paths;
4550
+ }
4551
+ fillSketch(drawing, o) {
4552
+ let fweight = o.fillWeight;
4553
+ if (fweight < 0) {
4554
+ fweight = o.strokeWidth / 2;
4555
+ }
4556
+ return {
4557
+ d: this.opsToPath(drawing),
4558
+ stroke: o.fill || NOS,
4559
+ strokeWidth: fweight,
4560
+ fill: NOS,
4561
+ };
4562
+ }
4563
+ _mergedShape(input) {
4564
+ return input.filter((d, i) => {
4565
+ if (i === 0) {
4566
+ return true;
4567
+ }
4568
+ if (d.op === 'move') {
4569
+ return false;
4570
+ }
4571
+ return true;
4572
+ });
4573
+ }
4574
+ }
4575
+
4576
+ class RoughCanvas {
4577
+ constructor(canvas, config) {
4578
+ this.canvas = canvas;
4579
+ this.ctx = this.canvas.getContext('2d');
4580
+ this.gen = new RoughGenerator(config);
4581
+ }
4582
+ draw(drawable) {
4583
+ const sets = drawable.sets || [];
4584
+ const o = drawable.options || this.getDefaultOptions();
4585
+ const ctx = this.ctx;
4586
+ const precision = drawable.options.fixedDecimalPlaceDigits;
4587
+ for (const drawing of sets) {
4588
+ switch (drawing.type) {
4589
+ case 'path':
4590
+ ctx.save();
4591
+ ctx.strokeStyle = o.stroke === 'none' ? 'transparent' : o.stroke;
4592
+ ctx.lineWidth = o.strokeWidth;
4593
+ if (o.strokeLineDash) {
4594
+ ctx.setLineDash(o.strokeLineDash);
4595
+ }
4596
+ if (o.strokeLineDashOffset) {
4597
+ ctx.lineDashOffset = o.strokeLineDashOffset;
4598
+ }
4599
+ this._drawToContext(ctx, drawing, precision);
4600
+ ctx.restore();
4601
+ break;
4602
+ case 'fillPath': {
4603
+ ctx.save();
4604
+ ctx.fillStyle = o.fill || '';
4605
+ const fillRule = (drawable.shape === 'curve' || drawable.shape === 'polygon' || drawable.shape === 'path') ? 'evenodd' : 'nonzero';
4606
+ this._drawToContext(ctx, drawing, precision, fillRule);
4607
+ ctx.restore();
4608
+ break;
4609
+ }
4610
+ case 'fillSketch':
4611
+ this.fillSketch(ctx, drawing, o);
4612
+ break;
4613
+ }
4614
+ }
4615
+ }
4616
+ fillSketch(ctx, drawing, o) {
4617
+ let fweight = o.fillWeight;
4618
+ if (fweight < 0) {
4619
+ fweight = o.strokeWidth / 2;
4620
+ }
4621
+ ctx.save();
4622
+ if (o.fillLineDash) {
4623
+ ctx.setLineDash(o.fillLineDash);
4624
+ }
4625
+ if (o.fillLineDashOffset) {
4626
+ ctx.lineDashOffset = o.fillLineDashOffset;
4627
+ }
4628
+ ctx.strokeStyle = o.fill || '';
4629
+ ctx.lineWidth = fweight;
4630
+ this._drawToContext(ctx, drawing, o.fixedDecimalPlaceDigits);
4631
+ ctx.restore();
4632
+ }
4633
+ _drawToContext(ctx, drawing, fixedDecimals, rule = 'nonzero') {
4634
+ ctx.beginPath();
4635
+ for (const item of drawing.ops) {
4636
+ const data = ((typeof fixedDecimals === 'number') && fixedDecimals >= 0) ? (item.data.map((d) => +d.toFixed(fixedDecimals))) : item.data;
4637
+ switch (item.op) {
4638
+ case 'move':
4639
+ ctx.moveTo(data[0], data[1]);
4640
+ break;
4641
+ case 'bcurveTo':
4642
+ ctx.bezierCurveTo(data[0], data[1], data[2], data[3], data[4], data[5]);
4643
+ break;
4644
+ case 'lineTo':
4645
+ ctx.lineTo(data[0], data[1]);
4646
+ break;
4647
+ }
4648
+ }
4649
+ if (drawing.type === 'fillPath') {
4650
+ ctx.fill(rule);
4651
+ }
4652
+ else {
4653
+ ctx.stroke();
4654
+ }
4655
+ }
4656
+ get generator() {
4657
+ return this.gen;
4658
+ }
4659
+ getDefaultOptions() {
4660
+ return this.gen.defaultOptions;
4661
+ }
4662
+ line(x1, y1, x2, y2, options) {
4663
+ const d = this.gen.line(x1, y1, x2, y2, options);
4664
+ this.draw(d);
4665
+ return d;
4666
+ }
4667
+ rectangle(x, y, width, height, options) {
4668
+ const d = this.gen.rectangle(x, y, width, height, options);
4669
+ this.draw(d);
4670
+ return d;
4671
+ }
4672
+ ellipse(x, y, width, height, options) {
4673
+ const d = this.gen.ellipse(x, y, width, height, options);
4674
+ this.draw(d);
4675
+ return d;
4676
+ }
4677
+ circle(x, y, diameter, options) {
4678
+ const d = this.gen.circle(x, y, diameter, options);
4679
+ this.draw(d);
4680
+ return d;
4681
+ }
4682
+ linearPath(points, options) {
4683
+ const d = this.gen.linearPath(points, options);
4684
+ this.draw(d);
4685
+ return d;
4686
+ }
4687
+ polygon(points, options) {
4688
+ const d = this.gen.polygon(points, options);
4689
+ this.draw(d);
4690
+ return d;
4691
+ }
4692
+ arc(x, y, width, height, start, stop, closed = false, options) {
4693
+ const d = this.gen.arc(x, y, width, height, start, stop, closed, options);
4694
+ this.draw(d);
4695
+ return d;
4696
+ }
4697
+ curve(points, options) {
4698
+ const d = this.gen.curve(points, options);
4699
+ this.draw(d);
4700
+ return d;
4701
+ }
4702
+ path(d, options) {
4703
+ const drawing = this.gen.path(d, options);
4704
+ this.draw(drawing);
4705
+ return drawing;
4706
+ }
4707
+ }
4708
+
4709
+ const SVGNS = 'http://www.w3.org/2000/svg';
4710
+
4711
+ class RoughSVG {
4712
+ constructor(svg, config) {
4713
+ this.svg = svg;
4714
+ this.gen = new RoughGenerator(config);
4715
+ }
4716
+ draw(drawable) {
4717
+ const sets = drawable.sets || [];
4718
+ const o = drawable.options || this.getDefaultOptions();
4719
+ const doc = this.svg.ownerDocument || window.document;
4720
+ const g = doc.createElementNS(SVGNS, 'g');
4721
+ const precision = drawable.options.fixedDecimalPlaceDigits;
4722
+ for (const drawing of sets) {
4723
+ let path = null;
4724
+ switch (drawing.type) {
4725
+ case 'path': {
4726
+ path = doc.createElementNS(SVGNS, 'path');
4727
+ path.setAttribute('d', this.opsToPath(drawing, precision));
4728
+ path.setAttribute('stroke', o.stroke);
4729
+ path.setAttribute('stroke-width', o.strokeWidth + '');
4730
+ path.setAttribute('fill', 'none');
4731
+ if (o.strokeLineDash) {
4732
+ path.setAttribute('stroke-dasharray', o.strokeLineDash.join(' ').trim());
4733
+ }
4734
+ if (o.strokeLineDashOffset) {
4735
+ path.setAttribute('stroke-dashoffset', `${o.strokeLineDashOffset}`);
4736
+ }
4737
+ break;
4738
+ }
4739
+ case 'fillPath': {
4740
+ path = doc.createElementNS(SVGNS, 'path');
4741
+ path.setAttribute('d', this.opsToPath(drawing, precision));
4742
+ path.setAttribute('stroke', 'none');
4743
+ path.setAttribute('stroke-width', '0');
4744
+ path.setAttribute('fill', o.fill || '');
4745
+ if (drawable.shape === 'curve' || drawable.shape === 'polygon') {
4746
+ path.setAttribute('fill-rule', 'evenodd');
4747
+ }
4748
+ break;
4749
+ }
4750
+ case 'fillSketch': {
4751
+ path = this.fillSketch(doc, drawing, o);
4752
+ break;
4753
+ }
4754
+ }
4755
+ if (path) {
4756
+ g.appendChild(path);
4757
+ }
4758
+ }
4759
+ return g;
4760
+ }
4761
+ fillSketch(doc, drawing, o) {
4762
+ let fweight = o.fillWeight;
4763
+ if (fweight < 0) {
4764
+ fweight = o.strokeWidth / 2;
4765
+ }
4766
+ const path = doc.createElementNS(SVGNS, 'path');
4767
+ path.setAttribute('d', this.opsToPath(drawing, o.fixedDecimalPlaceDigits));
4768
+ path.setAttribute('stroke', o.fill || '');
4769
+ path.setAttribute('stroke-width', fweight + '');
4770
+ path.setAttribute('fill', 'none');
4771
+ if (o.fillLineDash) {
4772
+ path.setAttribute('stroke-dasharray', o.fillLineDash.join(' ').trim());
4773
+ }
4774
+ if (o.fillLineDashOffset) {
4775
+ path.setAttribute('stroke-dashoffset', `${o.fillLineDashOffset}`);
4776
+ }
4777
+ return path;
4778
+ }
4779
+ get generator() {
4780
+ return this.gen;
4781
+ }
4782
+ getDefaultOptions() {
4783
+ return this.gen.defaultOptions;
4784
+ }
4785
+ opsToPath(drawing, fixedDecimalPlaceDigits) {
4786
+ return this.gen.opsToPath(drawing, fixedDecimalPlaceDigits);
4787
+ }
4788
+ line(x1, y1, x2, y2, options) {
4789
+ const d = this.gen.line(x1, y1, x2, y2, options);
4790
+ return this.draw(d);
4791
+ }
4792
+ rectangle(x, y, width, height, options) {
4793
+ const d = this.gen.rectangle(x, y, width, height, options);
4794
+ return this.draw(d);
4795
+ }
4796
+ ellipse(x, y, width, height, options) {
4797
+ const d = this.gen.ellipse(x, y, width, height, options);
4798
+ return this.draw(d);
4799
+ }
4800
+ circle(x, y, diameter, options) {
4801
+ const d = this.gen.circle(x, y, diameter, options);
4802
+ return this.draw(d);
4803
+ }
4804
+ linearPath(points, options) {
4805
+ const d = this.gen.linearPath(points, options);
4806
+ return this.draw(d);
4807
+ }
4808
+ polygon(points, options) {
4809
+ const d = this.gen.polygon(points, options);
4810
+ return this.draw(d);
4811
+ }
4812
+ arc(x, y, width, height, start, stop, closed = false, options) {
4813
+ const d = this.gen.arc(x, y, width, height, start, stop, closed, options);
4814
+ return this.draw(d);
4815
+ }
4816
+ curve(points, options) {
4817
+ const d = this.gen.curve(points, options);
4818
+ return this.draw(d);
4819
+ }
4820
+ path(d, options) {
4821
+ const drawing = this.gen.path(d, options);
4822
+ return this.draw(drawing);
4823
+ }
4824
+ }
4825
+
4826
+ var rough = {
4827
+ canvas(canvas, config) {
4828
+ return new RoughCanvas(canvas, config);
4829
+ },
4830
+ svg(svg, config) {
4831
+ return new RoughSVG(svg, config);
4832
+ },
4833
+ generator(config) {
4834
+ return new RoughGenerator(config);
4835
+ },
4836
+ newSeed() {
4837
+ return RoughGenerator.newSeed();
4838
+ },
4839
+ };
4840
+
2767
4841
  // ============================================================
2768
4842
  // sketchmark — SVG Renderer (rough.js hand-drawn)
2769
4843
  // ============================================================
@@ -5243,89 +7317,6 @@ class EventEmitter {
5243
7317
  }
5244
7318
  }
5245
7319
 
5246
- // ============================================================
5247
- // sketchmark — Encrypted sharing
5248
- // Diagram DSL is encrypted in the browser.
5249
- // The server stores an opaque blob it cannot read.
5250
- // The decryption key lives only in the URL fragment (#key=...).
5251
- // ============================================================
5252
- const WORKER_URL = 'https://sketchmark.anmism.workers.dev';
5253
- // ── Crypto helpers ────────────────────────────────────────
5254
- async function generateKey() {
5255
- return crypto.subtle.generateKey({ name: 'AES-GCM', length: 256 }, true, // extractable so we can export to URL
5256
- ['encrypt', 'decrypt']);
5257
- }
5258
- async function keyToBase64(key) {
5259
- const raw = await crypto.subtle.exportKey('raw', key);
5260
- return btoa(String.fromCharCode(...new Uint8Array(raw)));
5261
- }
5262
- async function base64ToKey(b64) {
5263
- const raw = Uint8Array.from(atob(b64), c => c.charCodeAt(0));
5264
- return crypto.subtle.importKey('raw', raw, { name: 'AES-GCM' }, false, // not extractable on the receiving end
5265
- ['decrypt']);
5266
- }
5267
- // ── Encrypt ───────────────────────────────────────────────
5268
- async function encryptDSL(dsl, key) {
5269
- const iv = crypto.getRandomValues(new Uint8Array(12));
5270
- const encoded = new TextEncoder().encode(dsl);
5271
- const encrypted = await crypto.subtle.encrypt({ name: 'AES-GCM', iv }, key, encoded);
5272
- // prepend iv to the blob: [ iv (12 bytes) | ciphertext ]
5273
- const result = new Uint8Array(12 + encrypted.byteLength);
5274
- result.set(iv, 0);
5275
- result.set(new Uint8Array(encrypted), 12);
5276
- return result;
5277
- }
5278
- // ── Decrypt ───────────────────────────────────────────────
5279
- async function decryptBlob(blob, key) {
5280
- const iv = blob.slice(0, 12);
5281
- const ciphertext = blob.slice(12);
5282
- const decrypted = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ciphertext);
5283
- return new TextDecoder().decode(decrypted);
5284
- }
5285
- // ── Public API ────────────────────────────────────────────
5286
- /**
5287
- * Encrypt DSL, upload to worker, return shareable URL.
5288
- * The URL fragment (#key=...) never reaches the server.
5289
- */
5290
- async function shareDiagram(dsl) {
5291
- const key = await generateKey();
5292
- const blob = await encryptDSL(dsl, key);
5293
- const keyB64 = await keyToBase64(key);
5294
- const res = await fetch(`${WORKER_URL}/api/blob`, {
5295
- method: 'POST',
5296
- headers: { 'Content-Type': 'application/octet-stream' },
5297
- body: blob.buffer,
5298
- });
5299
- if (!res.ok)
5300
- throw new Error(`Upload failed: ${res.status}`);
5301
- const { id } = await res.json();
5302
- // key goes into the fragment — browser never sends this to any server
5303
- return `${window.location.origin}/sketchmark/playground.html?s=${id}#key=${keyB64}`;
5304
- }
5305
- /**
5306
- * Read ?s= and #key= from the current URL, fetch + decrypt the diagram.
5307
- * Returns null if no share params found.
5308
- */
5309
- async function loadSharedDiagram() {
5310
- const params = new URLSearchParams(window.location.search);
5311
- const id = params.get('s');
5312
- if (!id)
5313
- return null;
5314
- // key is in the fragment — parse manually, not via URLSearchParams
5315
- // (URLSearchParams on hash strips the #)
5316
- const fragment = window.location.hash.slice(1);
5317
- const keyMatch = fragment.match(/key=([^&]+)/);
5318
- if (!keyMatch)
5319
- return null;
5320
- const keyB64 = keyMatch[1];
5321
- const res = await fetch(`${WORKER_URL}/api/blob/${id}`);
5322
- if (!res.ok)
5323
- throw new Error('Diagram not found or expired');
5324
- const blob = await res.arrayBuffer();
5325
- const key = await base64ToKey(keyB64);
5326
- return decryptBlob(blob, key);
5327
- }
5328
-
5329
7320
  // ============================================================
5330
7321
  // sketchmark — Public API
5331
7322
  // ============================================================
@@ -5417,7 +7408,6 @@ exports.layout = layout;
5417
7408
  exports.lerp = lerp;
5418
7409
  exports.listThemes = listThemes;
5419
7410
  exports.loadFont = loadFont;
5420
- exports.loadSharedDiagram = loadSharedDiagram;
5421
7411
  exports.markdownMap = markdownMap;
5422
7412
  exports.nodeMap = nodeMap;
5423
7413
  exports.parse = parse;
@@ -5428,7 +7418,6 @@ exports.renderToCanvas = renderToCanvas;
5428
7418
  exports.renderToSVG = renderToSVG;
5429
7419
  exports.resolveFont = resolveFont;
5430
7420
  exports.resolvePalette = resolvePalette;
5431
- exports.shareDiagram = shareDiagram;
5432
7421
  exports.sleep = sleep;
5433
7422
  exports.svgToPNGDataURL = svgToPNGDataURL;
5434
7423
  exports.svgToString = svgToString;