maidraw 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/assets/themes/common/finale/achievement/a.png +0 -0
  2. package/assets/themes/common/finale/achievement/aa.png +0 -0
  3. package/assets/themes/common/finale/achievement/aaa.png +0 -0
  4. package/assets/themes/common/finale/achievement/b.png +0 -0
  5. package/assets/themes/common/finale/achievement/c.png +0 -0
  6. package/assets/themes/common/finale/achievement/d.png +0 -0
  7. package/assets/themes/common/finale/achievement/e.png +0 -0
  8. package/assets/themes/common/finale/achievement/f.png +0 -0
  9. package/assets/themes/common/finale/achievement/s.png +0 -0
  10. package/assets/themes/common/finale/achievement/sp.png +0 -0
  11. package/assets/themes/common/finale/achievement/ss.png +0 -0
  12. package/assets/themes/common/finale/achievement/ssp.png +0 -0
  13. package/assets/themes/common/finale/achievement/sss.png +0 -0
  14. package/assets/themes/common/finale/achievement/sssp.png +0 -0
  15. package/assets/themes/common/finale/background.png +0 -0
  16. package/assets/themes/common/finale/background2.png +0 -0
  17. package/assets/themes/common/finale/logo.png +0 -0
  18. package/assets/themes/common/finale/milestone/ap.png +0 -0
  19. package/assets/themes/common/finale/milestone/app.png +0 -0
  20. package/assets/themes/common/finale/milestone/fc.png +0 -0
  21. package/assets/themes/common/finale/milestone/fcp.png +0 -0
  22. package/assets/themes/common/finale/milestone/fdx.png +0 -0
  23. package/assets/themes/common/finale/milestone/fdxp.png +0 -0
  24. package/assets/themes/common/finale/milestone/fsp.png +0 -0
  25. package/assets/themes/common/finale/milestone/sync.png +0 -0
  26. package/assets/themes/common/versionless/void.png +0 -0
  27. package/assets/themes/jp/finaleLandscape/manifest.json +128 -0
  28. package/assets/themes/jp/finalePortrait/manifest.json +128 -0
  29. package/dist/index.d.ts +2 -31
  30. package/dist/index.js +3 -895
  31. package/dist/index.js.map +1 -1
  32. package/dist/mai/best50/index.d.ts +34 -0
  33. package/dist/mai/best50/index.js +890 -0
  34. package/dist/mai/best50/index.js.map +1 -0
  35. package/dist/mai/best50/lib/divingFish/index.d.ts +82 -0
  36. package/dist/{lib → mai/best50/lib}/divingFish/index.js +68 -20
  37. package/dist/mai/best50/lib/divingFish/index.js.map +1 -0
  38. package/dist/{lib → mai/best50/lib}/index.d.ts +3 -3
  39. package/dist/{lib → mai/best50/lib}/index.js +1 -1
  40. package/dist/mai/best50/lib/index.js.map +1 -0
  41. package/dist/mai/best50/lib/kamaiTachi/index.d.ts +170 -0
  42. package/dist/mai/best50/lib/kamaiTachi/index.js +358 -0
  43. package/dist/mai/best50/lib/kamaiTachi/index.js.map +1 -0
  44. package/dist/{lib → mai/best50/lib}/lxns/index.d.ts +15 -7
  45. package/dist/{lib → mai/best50/lib}/lxns/index.js +42 -16
  46. package/dist/mai/best50/lib/lxns/index.js.map +1 -0
  47. package/dist/mai/chart/database.d.ts +11 -0
  48. package/dist/mai/chart/database.js +63 -0
  49. package/dist/mai/chart/database.js.map +1 -0
  50. package/dist/mai/chart/index.d.ts +4 -0
  51. package/dist/mai/chart/index.js +9 -0
  52. package/dist/mai/chart/index.js.map +1 -0
  53. package/dist/mai/index.d.ts +6 -0
  54. package/dist/mai/index.js +11 -0
  55. package/dist/mai/index.js.map +1 -0
  56. package/package.json +1 -1
  57. package/dist/lib/divingFish/index.d.ts +0 -50
  58. package/dist/lib/divingFish/index.js.map +0 -1
  59. package/dist/lib/index.js.map +0 -1
  60. package/dist/lib/kamaiTachi/index.d.ts +0 -111
  61. package/dist/lib/kamaiTachi/index.js +0 -194
  62. package/dist/lib/kamaiTachi/index.js.map +0 -1
  63. package/dist/lib/lxns/index.js.map +0 -1
  64. package/test/cn-2024-landscape.webp +0 -0
  65. package/test/cn-2024-portrait.webp +0 -0
  66. package/test/fetchConvert.js +0 -65
  67. package/test/jp-buddies-landscape.webp +0 -0
  68. package/test/jp-buddies-portrait.webp +0 -0
  69. package/test/jp-buddiesplus-landscape.webp +0 -0
  70. package/test/jp-buddiesplus-portrait.webp +0 -0
  71. package/test/jp-prism-landscape.webp +0 -0
  72. package/test/jp-prism-portrait.webp +0 -0
  73. package/test/jp-prismplus-landscape.webp +0 -0
  74. package/test/jp-prismplus-portrait.webp +0 -0
  75. package/test/testDraw.js +0 -745
@@ -0,0 +1,890 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.Best50 = exports.DivingFish = exports.KamaiTachi = exports.LXNS = void 0;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const upath_1 = __importDefault(require("upath"));
9
+ const type_1 = require("../../type");
10
+ const canvas_1 = require("canvas");
11
+ const color_1 = __importDefault(require("color"));
12
+ const sharp_1 = __importDefault(require("sharp"));
13
+ const glob_1 = require("glob");
14
+ const chart_1 = require("../chart");
15
+ const lxns_1 = require("./lib/lxns");
16
+ const kamaiTachi_1 = require("./lib/kamaiTachi");
17
+ const divingFish_1 = require("./lib/divingFish");
18
+ var lxns_2 = require("./lib/lxns");
19
+ Object.defineProperty(exports, "LXNS", { enumerable: true, get: function () { return lxns_2.LXNS; } });
20
+ var kamaiTachi_2 = require("./lib/kamaiTachi");
21
+ Object.defineProperty(exports, "KamaiTachi", { enumerable: true, get: function () { return kamaiTachi_2.KamaiTachi; } });
22
+ var divingFish_2 = require("./lib/divingFish");
23
+ Object.defineProperty(exports, "DivingFish", { enumerable: true, get: function () { return divingFish_2.DivingFish; } });
24
+ class HalfFullWidthConvert {
25
+ static charsets = {
26
+ latin: { halfRE: /[!-~]/g, fullRE: /[!-~]/g, delta: 0xfee0 },
27
+ hangul1: { halfRE: /[ᄀ-ᄒ]/g, fullRE: /[ᆨ-ᇂ]/g, delta: -0xedf9 },
28
+ hangul2: { halfRE: /[ᅡ-ᅵ]/g, fullRE: /[ᅡ-ᅵ]/g, delta: -0xee61 },
29
+ kana: {
30
+ delta: 0,
31
+ half: "。「」、・ヲァィゥェォャュョッーアイウエオカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン゙゚",
32
+ full: "。「」、・ヲァィゥェォャュョッーアイウエオカキクケコサシ" +
33
+ "スセソタチツテトナニヌネノハヒフヘホマミムメモヤユヨラリルレロワン゛゜",
34
+ },
35
+ extras: {
36
+ delta: 0,
37
+ half: "¢£¬¯¦¥₩\u0020|←↑→↓■°",
38
+ full: "¢£¬ ̄¦¥₩\u3000│←↑→↓■○",
39
+ },
40
+ };
41
+ // @ts-ignore
42
+ static toFull = (set) => (c) => set.delta
43
+ ? String.fromCharCode(c.charCodeAt(0) + set.delta)
44
+ : [...set.full][[...set.half].indexOf(c)];
45
+ // @ts-ignore
46
+ static toHalf = (set) => (c) => set.delta
47
+ ? String.fromCharCode(c.charCodeAt(0) - set.delta)
48
+ : [...set.half][[...set.full].indexOf(c)];
49
+ // @ts-ignore
50
+ static re = (set, way) => set[way + "RE"] || new RegExp("[" + set[way] + "]", "g");
51
+ static sets = Object.values(this.charsets);
52
+ // @ts-ignore
53
+ static toFullWidth = (str0) => this.sets.reduce((str, set) => str.replace(this.re(set, "half"), this.toFull(set)), str0);
54
+ // @ts-ignore
55
+ static toHalfWidth = (str0) => this.sets.reduce((str, set) => str.replace(this.re(set, "full"), this.toHalf(set)), str0);
56
+ }
57
+ class Best50 {
58
+ static LXNS = lxns_1.LXNS;
59
+ static KamaiTachi = kamaiTachi_1.KamaiTachi;
60
+ static DivingFish = divingFish_1.DivingFish;
61
+ static DEFAULT_THEME = "jp-prism-landscape";
62
+ static get assetsPath() {
63
+ return upath_1.default.join(__dirname, "..", "..", "..", "assets");
64
+ }
65
+ static themes = {};
66
+ static hasTheme(name) {
67
+ return !!this.themes[name];
68
+ }
69
+ static {
70
+ const manifests = (0, glob_1.globSync)(upath_1.default.join(this.assetsPath, "themes", "**", "manifest.json"));
71
+ for (const manifestPath of manifests) {
72
+ const manifest = JSON.parse(fs_1.default.readFileSync(manifestPath, "utf-8"));
73
+ if (this.validateManifest(manifest, upath_1.default.dirname(manifestPath))) {
74
+ this.themes[manifest.name] = upath_1.default.dirname(manifestPath);
75
+ }
76
+ }
77
+ const loadThemeResult = this.loadTheme(this.DEFAULT_THEME);
78
+ if (!loadThemeResult) {
79
+ console.error("Failed to load theme.");
80
+ }
81
+ (0, canvas_1.registerFont)(upath_1.default.join(this.assetsPath, "fonts", "gen-jyuu-gothic", "GenJyuuGothic-Bold.ttf"), {
82
+ family: "standard-font-title-jp",
83
+ });
84
+ (0, canvas_1.registerFont)(upath_1.default.join(this.assetsPath, "fonts", "comfortaa", "Comfortaa-Bold.ttf"), {
85
+ family: "standard-font-title-latin",
86
+ weight: "regular",
87
+ });
88
+ (0, canvas_1.registerFont)(upath_1.default.join(this.assetsPath, "fonts", "seurat-db", "FOT-Seurat Pro DB.otf"), {
89
+ family: "standard-font-username",
90
+ weight: "regular",
91
+ });
92
+ }
93
+ static validateManifest(payload, path) {
94
+ function isFileExist(p) {
95
+ if (isString(p))
96
+ return fs_1.default.existsSync(upath_1.default.join(path, p));
97
+ else
98
+ return false;
99
+ }
100
+ function isOneOf(p, ...args) {
101
+ return args.includes(p);
102
+ }
103
+ function isString(p) {
104
+ return typeof p == "string";
105
+ }
106
+ function isNumber(p) {
107
+ return typeof p == "number";
108
+ }
109
+ function isArray(p) {
110
+ return Array.isArray(p);
111
+ }
112
+ function isObject(p) {
113
+ return typeof p == "object";
114
+ }
115
+ function isHexColor(p) {
116
+ if (isString(p))
117
+ return /^(?:#[0-9A-F]{6}|#[0-9A-F]{8})$/i.test(p);
118
+ else
119
+ return false;
120
+ }
121
+ if (isString(payload.displayName) &&
122
+ isString(payload.name) &&
123
+ isNumber(payload.width) &&
124
+ isNumber(payload.height) &&
125
+ isObject(payload.sprites) &&
126
+ isObject(payload.sprites.achievement) &&
127
+ isFileExist(payload.sprites.achievement.d) &&
128
+ isFileExist(payload.sprites.achievement.c) &&
129
+ isFileExist(payload.sprites.achievement.b) &&
130
+ isFileExist(payload.sprites.achievement.bb) &&
131
+ isFileExist(payload.sprites.achievement.bbb) &&
132
+ isFileExist(payload.sprites.achievement.a) &&
133
+ isFileExist(payload.sprites.achievement.aa) &&
134
+ isFileExist(payload.sprites.achievement.aaa) &&
135
+ isFileExist(payload.sprites.achievement.s) &&
136
+ isFileExist(payload.sprites.achievement.sp) &&
137
+ isFileExist(payload.sprites.achievement.ss) &&
138
+ isFileExist(payload.sprites.achievement.ssp) &&
139
+ isFileExist(payload.sprites.achievement.sss) &&
140
+ isFileExist(payload.sprites.achievement.sssp) &&
141
+ isObject(payload.sprites.mode) &&
142
+ isFileExist(payload.sprites.mode.standard) &&
143
+ isFileExist(payload.sprites.mode.dx) &&
144
+ isObject(payload.sprites.milestone) &&
145
+ isFileExist(payload.sprites.milestone.ap) &&
146
+ isFileExist(payload.sprites.milestone.app) &&
147
+ isFileExist(payload.sprites.milestone.fc) &&
148
+ isFileExist(payload.sprites.milestone.fcp) &&
149
+ isFileExist(payload.sprites.milestone.fdx) &&
150
+ isFileExist(payload.sprites.milestone.fdxp) &&
151
+ isFileExist(payload.sprites.milestone.fs) &&
152
+ isFileExist(payload.sprites.milestone.fsp) &&
153
+ isFileExist(payload.sprites.milestone.sync) &&
154
+ isFileExist(payload.sprites.milestone.none) &&
155
+ isObject(payload.sprites.dxRating) &&
156
+ isFileExist(payload.sprites.dxRating.white) &&
157
+ isFileExist(payload.sprites.dxRating.blue) &&
158
+ isFileExist(payload.sprites.dxRating.green) &&
159
+ isFileExist(payload.sprites.dxRating.yellow) &&
160
+ isFileExist(payload.sprites.dxRating.red) &&
161
+ isFileExist(payload.sprites.dxRating.purple) &&
162
+ isFileExist(payload.sprites.dxRating.bronze) &&
163
+ isFileExist(payload.sprites.dxRating.silver) &&
164
+ isFileExist(payload.sprites.dxRating.gold) &&
165
+ isFileExist(payload.sprites.dxRating.platinum) &&
166
+ isFileExist(payload.sprites.dxRating.rainbow) &&
167
+ isObject(payload.sprites.profile) &&
168
+ isFileExist(payload.sprites.profile.icon) &&
169
+ isFileExist(payload.sprites.profile.nameplate) &&
170
+ isFileExist(payload.sprites.dxRatingNumberMap) &&
171
+ isArray(payload.elements)) {
172
+ for (const element of payload.elements) {
173
+ if (isNumber(element.x) && isNumber(element.y)) {
174
+ switch (element.type) {
175
+ case "image": {
176
+ if (isNumber(element.width) &&
177
+ isNumber(element.height) &&
178
+ isFileExist(element.path)) {
179
+ continue;
180
+ }
181
+ else
182
+ return false;
183
+ }
184
+ case "score-grid": {
185
+ if (isOneOf(element.region, "old", "new") &&
186
+ isNumber(element.horizontalSize) &&
187
+ isNumber(element.verticalSize) &&
188
+ isNumber(element.index) &&
189
+ isObject(element.scoreBubble) &&
190
+ isNumber(element.scoreBubble.width) &&
191
+ isNumber(element.scoreBubble.height) &&
192
+ isNumber(element.scoreBubble.margin) &&
193
+ isNumber(element.scoreBubble.gap) &&
194
+ isObject(element.scoreBubble.color) &&
195
+ isHexColor(element.scoreBubble.color.basic) &&
196
+ isHexColor(element.scoreBubble.color.advanced) &&
197
+ isHexColor(element.scoreBubble.color.expert) &&
198
+ isHexColor(element.scoreBubble.color.master) &&
199
+ isHexColor(element.scoreBubble.color.remaster) &&
200
+ isHexColor(element.scoreBubble.color.utage)) {
201
+ continue;
202
+ }
203
+ else
204
+ return false;
205
+ }
206
+ case "profile": {
207
+ if (isNumber(element.height)) {
208
+ continue;
209
+ }
210
+ else
211
+ return false;
212
+ }
213
+ default:
214
+ return false;
215
+ }
216
+ }
217
+ }
218
+ return true;
219
+ }
220
+ else
221
+ return false;
222
+ }
223
+ static primaryTheme = null;
224
+ static primaryThemePath = null;
225
+ static loadTheme(path) {
226
+ const theme = this.getTheme(path);
227
+ if (theme) {
228
+ this.primaryTheme = theme.manifest;
229
+ this.primaryThemePath = theme.path;
230
+ return true;
231
+ }
232
+ else
233
+ return false;
234
+ }
235
+ static getTheme(path) {
236
+ if (!fs_1.default.existsSync(upath_1.default.join(this.assetsPath, path, "manifest.json"))) {
237
+ path = this.themes[path] ?? "";
238
+ }
239
+ else
240
+ path = upath_1.default.join(this.assetsPath, path);
241
+ if (fs_1.default.existsSync(upath_1.default.join(path, "manifest.json"))) {
242
+ const manifest = JSON.parse(fs_1.default.readFileSync(upath_1.default.join(path, "manifest.json"), "utf-8"));
243
+ if (this.validateManifest(manifest, path)) {
244
+ return { manifest, path };
245
+ }
246
+ }
247
+ return null;
248
+ }
249
+ static getThemeFile(path, themePath) {
250
+ if (typeof path == "string" &&
251
+ fs_1.default.existsSync(upath_1.default.join(themePath ?? this.primaryThemePath, path)))
252
+ return fs_1.default.readFileSync(upath_1.default.join(themePath ?? this.primaryThemePath, path));
253
+ else
254
+ return Buffer.from([]);
255
+ }
256
+ static async draw(name, rating, newScores, oldScores, options) {
257
+ function drawText(ctx, str, x, y, fontSize, linewidth, maxWidth, textAlign = "left", mainColor = "white", borderColor = "black", font = `"standard-font-title-latin", "standard-font-title-jp"`) {
258
+ function findMaxFitString(original) {
259
+ const metrics = ctx.measureText(original);
260
+ if (metrics.width <= maxWidth)
261
+ return original;
262
+ for (let i = 1; i < original.length; ++i) {
263
+ let cur = original.slice(0, original.length - i);
264
+ if (ctx.measureText(cur + "...").width <= maxWidth) {
265
+ while (cur[cur.length - 1] == " ") {
266
+ cur = cur.substring(0, cur.length - 1);
267
+ }
268
+ return cur.trim() + "...";
269
+ }
270
+ }
271
+ return original;
272
+ }
273
+ ctx.font = `${fontSize}px ${font}`;
274
+ str = findMaxFitString(str);
275
+ if (linewidth > 0) {
276
+ ctx.strokeStyle = borderColor;
277
+ ctx.lineWidth = linewidth;
278
+ ctx.lineCap = "round";
279
+ ctx.lineJoin = "round";
280
+ ctx.textAlign = textAlign;
281
+ ctx.strokeText(str, x, y);
282
+ }
283
+ ctx.fillStyle = mainColor;
284
+ ctx.font = `${fontSize}px ${font}`;
285
+ ctx.textAlign = textAlign;
286
+ ctx.fillText(str, x, y);
287
+ if (linewidth > 0) {
288
+ ctx.strokeStyle = mainColor;
289
+ ctx.lineWidth = linewidth / 8;
290
+ ctx.lineCap = "round";
291
+ ctx.lineJoin = "round";
292
+ ctx.font = `${fontSize}px ${font}`;
293
+ ctx.textAlign = textAlign;
294
+ ctx.strokeText(str, x, y);
295
+ }
296
+ }
297
+ await chart_1.Chart.Database.cacheJackets([
298
+ ...newScores.map((v) => v.chart.id),
299
+ ...oldScores.map((v) => v.chart.id),
300
+ ]);
301
+ let currentTheme = this.primaryTheme, currentThemePath = this.primaryThemePath;
302
+ if (options?.theme) {
303
+ const theme = this.getTheme(options.theme);
304
+ if (theme) {
305
+ currentTheme = theme.manifest;
306
+ currentThemePath = theme.path;
307
+ }
308
+ }
309
+ if (currentTheme && currentThemePath) {
310
+ const canvas = new canvas_1.Canvas(currentTheme.width * (options?.scale ?? 1), currentTheme.height * (options?.scale ?? 1));
311
+ const ctx = canvas.getContext("2d");
312
+ if (options?.scale)
313
+ ctx.scale(options.scale, options.scale);
314
+ ctx.imageSmoothingEnabled = true;
315
+ for (const element of currentTheme.elements) {
316
+ switch (element.type) {
317
+ case "image": {
318
+ const img = new canvas_1.Image();
319
+ img.src = this.getThemeFile(element.path, currentThemePath);
320
+ ctx.drawImage(img, element.x, element.y, element.width, element.height);
321
+ break;
322
+ }
323
+ case "score-grid": {
324
+ const promises = [];
325
+ for (let cury = element.y, curindex = element.index, i = 0; i < element.verticalSize; ++i,
326
+ cury +=
327
+ element.scoreBubble.height +
328
+ element.scoreBubble.gap) {
329
+ for (let curx = element.x, j = 0; j < element.horizontalSize; ++j,
330
+ ++curindex,
331
+ curx +=
332
+ element.scoreBubble.width +
333
+ element.scoreBubble.gap) {
334
+ let curScore;
335
+ if (element.region == "new")
336
+ curScore = newScores[curindex];
337
+ else
338
+ curScore = oldScores[curindex];
339
+ if (curScore) {
340
+ let curColor = "#FFFFFF";
341
+ switch (curScore.chart.difficulty) {
342
+ case type_1.EDifficulty.BASIC:
343
+ curColor =
344
+ element.scoreBubble.color.basic;
345
+ break;
346
+ case type_1.EDifficulty.ADVANCED:
347
+ curColor =
348
+ element.scoreBubble.color
349
+ .advanced;
350
+ break;
351
+ case type_1.EDifficulty.EXPERT:
352
+ curColor =
353
+ element.scoreBubble.color
354
+ .expert;
355
+ break;
356
+ case type_1.EDifficulty.MASTER:
357
+ curColor =
358
+ element.scoreBubble.color
359
+ .master;
360
+ break;
361
+ case type_1.EDifficulty.REMASTER:
362
+ curColor =
363
+ element.scoreBubble.color
364
+ .remaster;
365
+ break;
366
+ case type_1.EDifficulty.UTAGE:
367
+ curColor =
368
+ element.scoreBubble.color.utage;
369
+ break;
370
+ }
371
+ /** Begin Card Draw */
372
+ ctx.save();
373
+ ctx.fillStyle = new color_1.default(curColor)
374
+ .lighten(0.4)
375
+ .hexa();
376
+ ctx.beginPath();
377
+ ctx.roundRect(curx, cury, element.scoreBubble.width, element.scoreBubble.height, (element.scoreBubble.height * 0.806) / 7);
378
+ ctx.strokeStyle = new color_1.default(curColor)
379
+ .darken(0.3)
380
+ .hexa();
381
+ ctx.lineWidth =
382
+ element.scoreBubble.margin / 4;
383
+ ctx.stroke();
384
+ ctx.fill();
385
+ ctx.beginPath();
386
+ ctx.roundRect(curx, cury, element.scoreBubble.width, element.scoreBubble.height, (element.scoreBubble.height * 0.806) / 7);
387
+ ctx.clip();
388
+ /** Begin Main Content Draw */
389
+ ctx.save();
390
+ ctx.beginPath();
391
+ ctx.roundRect(curx, cury, element.scoreBubble.width, element.scoreBubble.height * 0.742, (element.scoreBubble.height * 0.806) / 7);
392
+ ctx.clip();
393
+ ctx.fillStyle = curColor;
394
+ ctx.fill();
395
+ const jacketSize = Math.min(element.scoreBubble.width, element.scoreBubble.height * 0.742);
396
+ const jacketMaskGrad = ctx.createLinearGradient(curx + jacketSize / 2, cury + jacketSize / 2, curx + jacketSize, cury + jacketSize / 2);
397
+ jacketMaskGrad.addColorStop(0, new color_1.default(curColor).alpha(0).hexa());
398
+ jacketMaskGrad.addColorStop(0.25, new color_1.default(curColor).alpha(0.2).hexa());
399
+ jacketMaskGrad.addColorStop(1, new color_1.default(curColor).alpha(1).hexa());
400
+ const jacketMaskGradDark = ctx.createLinearGradient(curx + jacketSize / 2, cury + jacketSize / 2, curx + jacketSize, cury + jacketSize / 2);
401
+ jacketMaskGradDark.addColorStop(0, new color_1.default(curColor)
402
+ .darken(0.3)
403
+ .alpha(0)
404
+ .hexa());
405
+ jacketMaskGradDark.addColorStop(0.25, new color_1.default(curColor)
406
+ .darken(0.3)
407
+ .alpha(0.2)
408
+ .hexa());
409
+ jacketMaskGradDark.addColorStop(1, new color_1.default(curColor)
410
+ .darken(0.3)
411
+ .alpha(1)
412
+ .hexa());
413
+ /** Begin Jacket Draw*/
414
+ let jacket = await chart_1.Chart.Database.fecthJacket(curScore.chart.id);
415
+ if (!jacket)
416
+ jacket =
417
+ await chart_1.Chart.Database.fecthJacket(0);
418
+ if (jacket) {
419
+ const img = new canvas_1.Image();
420
+ img.src = jacket;
421
+ ctx.drawImage(img, curx, cury, jacketSize, jacketSize);
422
+ }
423
+ else {
424
+ ctx.fillStyle = "#b6ffab";
425
+ ctx.fillRect(curx, cury, jacketSize, jacketSize);
426
+ }
427
+ /** End Jacket Draw*/
428
+ /** Begin Jacket Gradient Mask Draw*/ {
429
+ ctx.fillStyle = jacketMaskGrad;
430
+ ctx.fillRect(curx + jacketSize / 2, cury, (jacketSize * 3) / 4, jacketSize);
431
+ } /** End Jacket Gradient Mask Draw*/
432
+ ctx.beginPath();
433
+ ctx.roundRect(curx + element.scoreBubble.margin, cury + element.scoreBubble.margin, element.scoreBubble.width -
434
+ element.scoreBubble.margin * 2, element.scoreBubble.height * 0.806 -
435
+ element.scoreBubble.margin * 2, (element.scoreBubble.height * 0.806 -
436
+ element.scoreBubble.margin * 2) /
437
+ 7);
438
+ /** Begin Title Draw */ {
439
+ drawText(ctx, curScore.chart.name, curx + (jacketSize * 7) / 8, cury +
440
+ element.scoreBubble.margin +
441
+ element.scoreBubble.height *
442
+ 0.806 *
443
+ 0.144, element.scoreBubble.height *
444
+ 0.806 *
445
+ 0.144, element.scoreBubble.height *
446
+ 0.806 *
447
+ 0.04, element.scoreBubble.width -
448
+ (jacketSize * 7) / 8 -
449
+ element.scoreBubble.margin, "left", "white", jacketMaskGradDark);
450
+ } /** End Title Draw */
451
+ /** Begin Separation Line Draw */ {
452
+ ctx.beginPath();
453
+ ctx.roundRect(curx + (jacketSize * 13) / 16, cury +
454
+ element.scoreBubble.margin +
455
+ element.scoreBubble.height *
456
+ 0.806 *
457
+ (0.144 + 0.072), element.scoreBubble.width -
458
+ (jacketSize * 13) / 16 -
459
+ element.scoreBubble.margin * 2, element.scoreBubble.height *
460
+ 0.806 *
461
+ 0.02, element.scoreBubble.height *
462
+ 0.806 *
463
+ 0.16);
464
+ ctx.fillStyle = jacketMaskGradDark;
465
+ ctx.fill();
466
+ } /** End Separation Line Draw */
467
+ /** Begin Achievement Rate Draw */
468
+ drawText(ctx, `${curScore.achievement.toFixed(4)}%`, curx -
469
+ element.scoreBubble.margin -
470
+ element.scoreBubble.height *
471
+ 0.806 *
472
+ 0.02 +
473
+ element.scoreBubble.width, cury +
474
+ element.scoreBubble.margin +
475
+ element.scoreBubble.height *
476
+ 0.806 *
477
+ (0.144 + 0.144 + 0.208 - 0.04), element.scoreBubble.height *
478
+ 0.806 *
479
+ 0.208, element.scoreBubble.height *
480
+ 0.806 *
481
+ 0.04, Infinity, "right", "white", new color_1.default(curColor).darken(0.3).hexa());
482
+ /** End Achievement Rate Draw */
483
+ /** Begin Achievement Rank Draw */
484
+ {
485
+ let rankImg;
486
+ switch (curScore.achievementRank) {
487
+ case type_1.EAchievementTypes.D:
488
+ rankImg = this.getThemeFile(currentTheme.sprites
489
+ .achievement.d, currentThemePath);
490
+ break;
491
+ case type_1.EAchievementTypes.C:
492
+ rankImg = this.getThemeFile(currentTheme.sprites
493
+ .achievement.c, currentThemePath);
494
+ break;
495
+ case type_1.EAchievementTypes.B:
496
+ rankImg = this.getThemeFile(currentTheme.sprites
497
+ .achievement.b, currentThemePath);
498
+ break;
499
+ case type_1.EAchievementTypes.BB:
500
+ rankImg = this.getThemeFile(currentTheme.sprites
501
+ .achievement.bb, currentThemePath);
502
+ break;
503
+ case type_1.EAchievementTypes.BBB:
504
+ rankImg = this.getThemeFile(currentTheme.sprites
505
+ .achievement.bbb, currentThemePath);
506
+ break;
507
+ case type_1.EAchievementTypes.A:
508
+ rankImg = this.getThemeFile(currentTheme.sprites
509
+ .achievement.a, currentThemePath);
510
+ break;
511
+ case type_1.EAchievementTypes.AA:
512
+ rankImg = this.getThemeFile(currentTheme.sprites
513
+ .achievement.aa, currentThemePath);
514
+ break;
515
+ case type_1.EAchievementTypes.AAA:
516
+ rankImg = this.getThemeFile(currentTheme.sprites
517
+ .achievement.aaa, currentThemePath);
518
+ break;
519
+ case type_1.EAchievementTypes.S:
520
+ rankImg = this.getThemeFile(currentTheme.sprites
521
+ .achievement.s, currentThemePath);
522
+ break;
523
+ case type_1.EAchievementTypes.SP:
524
+ rankImg = this.getThemeFile(currentTheme.sprites
525
+ .achievement.sp, currentThemePath);
526
+ break;
527
+ case type_1.EAchievementTypes.SS:
528
+ rankImg = this.getThemeFile(currentTheme.sprites
529
+ .achievement.ss, currentThemePath);
530
+ break;
531
+ case type_1.EAchievementTypes.SSP:
532
+ rankImg = this.getThemeFile(currentTheme.sprites
533
+ .achievement.ssp, currentThemePath);
534
+ break;
535
+ case type_1.EAchievementTypes.SSS:
536
+ rankImg = this.getThemeFile(currentTheme.sprites
537
+ .achievement.sss, currentThemePath);
538
+ break;
539
+ default:
540
+ rankImg = this.getThemeFile(currentTheme.sprites
541
+ .achievement.sssp, currentThemePath);
542
+ }
543
+ const img = new canvas_1.Image();
544
+ img.src = rankImg;
545
+ ctx.drawImage(img, curx + jacketSize, cury +
546
+ element.scoreBubble.margin +
547
+ element.scoreBubble.height *
548
+ 0.806 *
549
+ (0.144 +
550
+ 0.144 +
551
+ 0.208 +
552
+ 0.02), element.scoreBubble.height *
553
+ 0.806 *
554
+ 0.3 *
555
+ 2.133, element.scoreBubble.height *
556
+ 0.806 *
557
+ 0.3);
558
+ }
559
+ /** End Achievement Rank Draw */
560
+ /** Begin Milestone Draw */
561
+ {
562
+ let comboImg, syncImg;
563
+ switch (curScore.combo) {
564
+ case type_1.EComboTypes.NONE:
565
+ comboImg = this.getThemeFile(currentTheme.sprites
566
+ .milestone.none, currentThemePath);
567
+ break;
568
+ case type_1.EComboTypes.FULL_COMBO:
569
+ comboImg = this.getThemeFile(currentTheme.sprites
570
+ .milestone.fc, currentThemePath);
571
+ break;
572
+ case type_1.EComboTypes.FULL_COMBO_PLUS:
573
+ comboImg = this.getThemeFile(currentTheme.sprites
574
+ .milestone.fcp, currentThemePath);
575
+ break;
576
+ case type_1.EComboTypes.ALL_PERFECT:
577
+ comboImg = this.getThemeFile(currentTheme.sprites
578
+ .milestone.ap, currentThemePath);
579
+ break;
580
+ case type_1.EComboTypes.ALL_PERFECT_PLUS:
581
+ comboImg = this.getThemeFile(currentTheme.sprites
582
+ .milestone.app, currentThemePath);
583
+ break;
584
+ }
585
+ switch (curScore.sync) {
586
+ case type_1.ESyncTypes.NONE:
587
+ syncImg = this.getThemeFile(currentTheme.sprites
588
+ .milestone.none, currentThemePath);
589
+ break;
590
+ case type_1.ESyncTypes.SYNC_PLAY:
591
+ syncImg = this.getThemeFile(currentTheme.sprites
592
+ .milestone.sync, currentThemePath);
593
+ break;
594
+ case type_1.ESyncTypes.FULL_SYNC:
595
+ syncImg = this.getThemeFile(currentTheme.sprites
596
+ .milestone.fs, currentThemePath);
597
+ break;
598
+ case type_1.ESyncTypes.FULL_SYNC_PLUS:
599
+ syncImg = this.getThemeFile(currentTheme.sprites
600
+ .milestone.fsp, currentThemePath);
601
+ break;
602
+ case type_1.ESyncTypes.FULL_SYNC_DX:
603
+ syncImg = this.getThemeFile(currentTheme.sprites
604
+ .milestone.fdx, currentThemePath);
605
+ break;
606
+ case type_1.ESyncTypes.FULL_SYNC_DX_PLUS:
607
+ syncImg = this.getThemeFile(currentTheme.sprites
608
+ .milestone.fdxp, currentThemePath);
609
+ break;
610
+ }
611
+ const combo = new canvas_1.Image();
612
+ combo.src = comboImg;
613
+ ctx.drawImage(combo, curx +
614
+ (jacketSize * 7) / 8 +
615
+ element.scoreBubble.height *
616
+ 0.806 *
617
+ (0.32 * 2.133 + 0.06), cury +
618
+ element.scoreBubble.margin +
619
+ element.scoreBubble.height *
620
+ 0.806 *
621
+ (0.144 +
622
+ 0.144 +
623
+ 0.208 +
624
+ 0.01), element.scoreBubble.height *
625
+ 0.806 *
626
+ 0.32, element.scoreBubble.height *
627
+ 0.806 *
628
+ 0.32);
629
+ const sync = new canvas_1.Image();
630
+ sync.src = syncImg;
631
+ ctx.drawImage(sync, curx +
632
+ (jacketSize * 7) / 8 +
633
+ element.scoreBubble.height *
634
+ 0.806 *
635
+ (0.32 * 2.133 +
636
+ 0.04 +
637
+ 0.32), cury +
638
+ element.scoreBubble.margin +
639
+ element.scoreBubble.height *
640
+ 0.806 *
641
+ (0.144 +
642
+ 0.144 +
643
+ 0.208 +
644
+ 0.01), element.scoreBubble.height *
645
+ 0.806 *
646
+ 0.32, element.scoreBubble.height *
647
+ 0.806 *
648
+ 0.32);
649
+ }
650
+ /** End Milestone Draw */
651
+ /** Begin Chart Mode Draw */
652
+ {
653
+ const mode = new canvas_1.Image();
654
+ const chartModeBadgeImg = this.getThemeFile(curScore.chart.id > 10000
655
+ ? currentTheme.sprites.mode
656
+ .dx
657
+ : currentTheme.sprites.mode
658
+ .standard, currentThemePath);
659
+ const { width, height } = await (0, sharp_1.default)(chartModeBadgeImg).metadata();
660
+ const aspectRatio = (width ?? 0) / (height ?? 1) || 3;
661
+ mode.src = chartModeBadgeImg;
662
+ const drawHeight = (jacketSize * 6) / 8;
663
+ ctx.drawImage(mode, curx +
664
+ ((jacketSize * 7) / 8 -
665
+ drawHeight) /
666
+ 2, cury +
667
+ element.scoreBubble.margin +
668
+ element.scoreBubble.height *
669
+ 0.806 *
670
+ 0.02, drawHeight, drawHeight / aspectRatio);
671
+ }
672
+ /** End Chart Mode Draw */
673
+ /** Begin Bests Index Draw */ {
674
+ drawText(ctx, `#${curindex + 1}`, curx +
675
+ element.scoreBubble.margin * 2, cury +
676
+ jacketSize -
677
+ element.scoreBubble.margin * 2, element.scoreBubble.height *
678
+ 0.806 *
679
+ 0.128, element.scoreBubble.height *
680
+ 0.806 *
681
+ 0.04, Infinity, "left", "white", new color_1.default(curColor)
682
+ .darken(0.3)
683
+ .hexa());
684
+ } /** End Bests Index Draw */
685
+ ctx.restore();
686
+ /** End Main Content Draw */
687
+ /** Begin Difficulty & DX Rating Draw */ {
688
+ drawText(ctx, `${curScore.chart.level.toFixed(1)} ↑${curScore.dxRating.toFixed(0)}`, curx +
689
+ element.scoreBubble.margin * 2, cury +
690
+ element.scoreBubble.height *
691
+ (0.806 + (1 - 0.806) / 2), element.scoreBubble.height *
692
+ 0.806 *
693
+ 0.128, element.scoreBubble.height *
694
+ 0.806 *
695
+ 0.04, Infinity, "left", "white", new color_1.default(curColor)
696
+ .darken(0.3)
697
+ .hexa());
698
+ if (curScore.chart.maxDxScore) {
699
+ drawText(ctx, `${curScore.dxScore}/${curScore.chart.maxDxScore}`, curx +
700
+ element.scoreBubble.width -
701
+ element.scoreBubble.margin *
702
+ 2, cury +
703
+ element.scoreBubble.height *
704
+ (0.806 +
705
+ (1 - 0.806) / 2), element.scoreBubble.height *
706
+ 0.806 *
707
+ 0.128, element.scoreBubble.height *
708
+ 0.806 *
709
+ 0.04, Infinity, "right", "white", new color_1.default(curColor)
710
+ .darken(0.3)
711
+ .hexa());
712
+ }
713
+ } /** End Difficulty & DX Rating Draw */
714
+ ctx.restore();
715
+ }
716
+ }
717
+ }
718
+ break;
719
+ }
720
+ case "profile": {
721
+ const nameplate = new canvas_1.Image();
722
+ nameplate.src = this.getThemeFile(currentTheme.sprites.profile.nameplate, currentThemePath);
723
+ ctx.drawImage(nameplate, element.x, element.y, element.height * 6.207, element.height);
724
+ /* Begin Profile Picture Draw */
725
+ ctx.save();
726
+ ctx.beginPath();
727
+ ctx.roundRect(element.x + element.height * 0.064, element.y + element.height * 0.064, element.height * 0.872, element.height * 0.872, (element.height * 0.872) / 16);
728
+ ctx.clip();
729
+ ctx.fillStyle = "white";
730
+ ctx.fill();
731
+ const profilePicture = options?.profilePicture ||
732
+ this.getThemeFile(currentTheme.sprites.profile.icon, currentThemePath);
733
+ const icon = new canvas_1.Image();
734
+ const { dominant } = await (0, sharp_1.default)(profilePicture).stats();
735
+ icon.src = profilePicture;
736
+ const cropSize = Math.min(icon.width, icon.height);
737
+ ctx.drawImage(icon, (icon.width - cropSize) / 2, (icon.height - cropSize) / 2, cropSize, cropSize, element.x + element.height * 0.064, element.y + element.height * 0.064, element.height * 0.872, element.height * 0.872);
738
+ if (options?.profilePicture) {
739
+ ctx.beginPath();
740
+ ctx.roundRect(element.x + element.height * 0.064, element.y + element.height * 0.064, element.height * 0.872, element.height * 0.872, (element.height * 0.872) / 16);
741
+ ctx.strokeStyle = color_1.default.rgb(dominant)
742
+ .darken(0.3)
743
+ .hex();
744
+ ctx.lineWidth = element.height / 30;
745
+ ctx.stroke();
746
+ }
747
+ ctx.restore();
748
+ /* End Profile Picture Draw */
749
+ const dxRating = new canvas_1.Image();
750
+ let dxRatingImg;
751
+ switch (true) {
752
+ case rating > 15000: {
753
+ dxRatingImg = this.getThemeFile(currentTheme.sprites.dxRating.rainbow, currentThemePath);
754
+ break;
755
+ }
756
+ case rating > 14500: {
757
+ dxRatingImg = this.getThemeFile(currentTheme.sprites.dxRating.platinum, currentThemePath);
758
+ break;
759
+ }
760
+ case rating > 14000: {
761
+ dxRatingImg = this.getThemeFile(currentTheme.sprites.dxRating.gold, currentThemePath);
762
+ break;
763
+ }
764
+ case rating > 13000: {
765
+ dxRatingImg = this.getThemeFile(currentTheme.sprites.dxRating.silver, currentThemePath);
766
+ break;
767
+ }
768
+ case rating > 12000: {
769
+ dxRatingImg = this.getThemeFile(currentTheme.sprites.dxRating.bronze, currentThemePath);
770
+ break;
771
+ }
772
+ case rating > 10000: {
773
+ dxRatingImg = this.getThemeFile(currentTheme.sprites.dxRating.purple, currentThemePath);
774
+ break;
775
+ }
776
+ case rating > 8000: {
777
+ dxRatingImg = this.getThemeFile(currentTheme.sprites.dxRating.red, currentThemePath);
778
+ break;
779
+ }
780
+ case rating > 6000: {
781
+ dxRatingImg = this.getThemeFile(currentTheme.sprites.dxRating.yellow, currentThemePath);
782
+ break;
783
+ }
784
+ case rating > 4000: {
785
+ dxRatingImg = this.getThemeFile(currentTheme.sprites.dxRating.green, currentThemePath);
786
+ break;
787
+ }
788
+ case rating > 2000: {
789
+ dxRatingImg = this.getThemeFile(currentTheme.sprites.dxRating.blue, currentThemePath);
790
+ break;
791
+ }
792
+ default: {
793
+ dxRatingImg = this.getThemeFile(currentTheme.sprites.dxRating.white, currentThemePath);
794
+ break;
795
+ }
796
+ }
797
+ dxRating.src = dxRatingImg;
798
+ ctx.drawImage(dxRating, element.x + element.height, element.y + element.height * 0.064, (element.height / 3) * 5.108, element.height / 3);
799
+ /* Start Username Draw */
800
+ ctx.beginPath();
801
+ ctx.roundRect(element.x + element.height * (1 + 1 / 32), element.y +
802
+ element.height * (0.064 + 0.333 + 1 / 32), ((element.height / 3) * 5.108 * 6) / 5, (element.height * 7) / 24, element.height / 20);
803
+ ctx.fillStyle = "white";
804
+ ctx.strokeStyle = color_1.default.rgb(180, 180, 180).hex();
805
+ ctx.lineWidth = element.height / 32;
806
+ ctx.stroke();
807
+ ctx.fill();
808
+ const ratingImgBuffer = await this.getRatingNumber(rating, {
809
+ manifest: currentTheme,
810
+ path: currentThemePath,
811
+ });
812
+ if (ratingImgBuffer) {
813
+ const { width, height } = await (0, sharp_1.default)(ratingImgBuffer).metadata();
814
+ if (width && height) {
815
+ const aspectRatio = width / height;
816
+ const image = new canvas_1.Image();
817
+ image.src = ratingImgBuffer;
818
+ const drawHeight = (element.height * 7) / 32;
819
+ ctx.drawImage(image, element.x + element.height * 1.785, element.y + element.height * 0.12, drawHeight * aspectRatio * 0.8, drawHeight);
820
+ }
821
+ }
822
+ drawText(ctx, HalfFullWidthConvert.toFullWidth(name), element.x + element.height * (1 + 1 / 16), element.y +
823
+ element.height * (0.064 + 0.333 + 1 / 4), (element.height * 1) / 6, 0, ((element.height / 3) * 5.108 * 6) / 5, "left", "black", "black", "standard-font-username");
824
+ /* End Username Draw*/
825
+ break;
826
+ }
827
+ }
828
+ }
829
+ return canvas.toBuffer();
830
+ }
831
+ else
832
+ return null;
833
+ }
834
+ static async drawWithScoreSource(source, username, options) {
835
+ const profile = await source.getPlayerInfo(username);
836
+ const score = await source.getPlayerBest50(username);
837
+ if (!profile || !score)
838
+ return null;
839
+ return this.draw(profile.name, profile.rating, score.new, score.old, {
840
+ ...options,
841
+ profilePicture: options?.profilePicture === null
842
+ ? undefined
843
+ : options?.profilePicture ||
844
+ (await source.getPlayerProfilePicture(username)) ||
845
+ undefined,
846
+ });
847
+ }
848
+ static async getRatingNumber(num, theme) {
849
+ async function getRaingDigit(map, digit, unitWidth, unitHeight) {
850
+ digit = Math.floor(digit % 10);
851
+ return await (0, sharp_1.default)(map)
852
+ .extract({
853
+ left: (digit % 4) * unitWidth,
854
+ top: Math.floor(digit / 4) * unitHeight,
855
+ width: unitWidth,
856
+ height: unitHeight,
857
+ })
858
+ .toBuffer();
859
+ }
860
+ if (theme.manifest) {
861
+ const map = this.getThemeFile(theme.manifest.sprites.dxRatingNumberMap, theme.path);
862
+ const { width, height } = await (0, sharp_1.default)(map).metadata();
863
+ if (!(width && height))
864
+ return null;
865
+ const unitWidth = width / 4, unitHeight = height / 4;
866
+ let digits = [];
867
+ while (num > 0) {
868
+ digits.push(await getRaingDigit(map, num % 10, unitWidth, unitHeight));
869
+ num = Math.floor(num / 10);
870
+ }
871
+ while (digits.length < 5)
872
+ digits.push(null);
873
+ digits = digits.reverse();
874
+ const canvas = new canvas_1.Canvas(unitWidth * digits.length, unitHeight);
875
+ const ctx = canvas.getContext("2d");
876
+ for (let i = 0; i < digits.length; ++i) {
877
+ const curDigit = digits[i];
878
+ if (!curDigit)
879
+ continue;
880
+ const img = new canvas_1.Image();
881
+ img.src = curDigit;
882
+ ctx.drawImage(img, unitWidth * i * 0.88, 0);
883
+ }
884
+ return canvas.toBuffer();
885
+ }
886
+ return null;
887
+ }
888
+ }
889
+ exports.Best50 = Best50;
890
+ //# sourceMappingURL=index.js.map