chess2img 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -3
- package/dist/index.cjs +187 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +21 -4
- package/dist/index.d.ts +21 -4
- package/dist/index.js +187 -51
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -161,6 +161,12 @@ function isCoordinatesOptions(value) {
|
|
|
161
161
|
function isCoordinatesPosition(value) {
|
|
162
162
|
return value === "border" || value === "inside";
|
|
163
163
|
}
|
|
164
|
+
function isHighlightOptions(value) {
|
|
165
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
166
|
+
}
|
|
167
|
+
function isHighlightStyle(value) {
|
|
168
|
+
return value === "fill" || value === "circle";
|
|
169
|
+
}
|
|
164
170
|
function validateCoordinatesOption(coordinates, borderSize) {
|
|
165
171
|
if (coordinates === void 0 || typeof coordinates === "boolean") {
|
|
166
172
|
return;
|
|
@@ -193,6 +199,49 @@ function validateCoordinatesOption(coordinates, borderSize) {
|
|
|
193
199
|
validateColorString(coordinates.color, "coordinates.color");
|
|
194
200
|
}
|
|
195
201
|
}
|
|
202
|
+
function validateHighlightEntry(entry) {
|
|
203
|
+
if (typeof entry === "string") {
|
|
204
|
+
validateSquare(entry);
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (!isHighlightOptions(entry)) {
|
|
208
|
+
throw new ValidationError("highlights entries must be square strings or highlight objects");
|
|
209
|
+
}
|
|
210
|
+
if (typeof entry.square !== "string") {
|
|
211
|
+
throw new ValidationError("highlight.square must be a valid algebraic square");
|
|
212
|
+
}
|
|
213
|
+
validateSquare(entry.square);
|
|
214
|
+
if (entry.style !== void 0 && !isHighlightStyle(entry.style)) {
|
|
215
|
+
throw new ValidationError("highlight.style must be 'fill' or 'circle'");
|
|
216
|
+
}
|
|
217
|
+
if (entry.color !== void 0) {
|
|
218
|
+
validateColorString(entry.color, "highlight.color");
|
|
219
|
+
}
|
|
220
|
+
if (entry.opacity !== void 0 && (!Number.isFinite(entry.opacity) || entry.opacity < 0 || entry.opacity > 1)) {
|
|
221
|
+
throw new ValidationError("highlight.opacity must be a finite number between 0 and 1");
|
|
222
|
+
}
|
|
223
|
+
if (entry.lineWidth !== void 0 && (!Number.isFinite(entry.lineWidth) || entry.lineWidth <= 0)) {
|
|
224
|
+
throw new ValidationError("highlight.lineWidth must be a finite number greater than 0");
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
function validateHighlightOptions(highlights) {
|
|
228
|
+
if (highlights === void 0) {
|
|
229
|
+
return;
|
|
230
|
+
}
|
|
231
|
+
if (!Array.isArray(highlights)) {
|
|
232
|
+
throw new ValidationError("highlights must be an array");
|
|
233
|
+
}
|
|
234
|
+
for (const entry of highlights) {
|
|
235
|
+
validateHighlightEntry(entry);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function validateHighlightsInput(highlights, highlightSquares) {
|
|
239
|
+
if (highlights !== void 0 && highlightSquares !== void 0) {
|
|
240
|
+
throw new ValidationError("Use either highlights or highlightSquares, not both");
|
|
241
|
+
}
|
|
242
|
+
validateHighlightOptions(highlights);
|
|
243
|
+
validateHighlightOptions(highlightSquares);
|
|
244
|
+
}
|
|
196
245
|
|
|
197
246
|
// src/core/parsers.ts
|
|
198
247
|
var PIECE_SYMBOL_TO_KEY = {
|
|
@@ -256,11 +305,6 @@ function parseBoardArray(board) {
|
|
|
256
305
|
return position;
|
|
257
306
|
}
|
|
258
307
|
|
|
259
|
-
// src/core/highlights.ts
|
|
260
|
-
function normalizeHighlights(input) {
|
|
261
|
-
return [...new Set(input.map(validateSquare))].sort();
|
|
262
|
-
}
|
|
263
|
-
|
|
264
308
|
// src/render/canvas-renderer.ts
|
|
265
309
|
import { createCanvas as createCanvas3 } from "canvas";
|
|
266
310
|
|
|
@@ -486,6 +530,19 @@ function resolveInsideLabelColor(request, square) {
|
|
|
486
530
|
}
|
|
487
531
|
return isDarkSquare(square) ? INSIDE_LIGHT_LABEL_COLOR : INSIDE_DARK_LABEL_COLOR;
|
|
488
532
|
}
|
|
533
|
+
function resolveHighlightOpacity(style, color, opacity) {
|
|
534
|
+
if (opacity !== void 0) {
|
|
535
|
+
return opacity;
|
|
536
|
+
}
|
|
537
|
+
if (style === "circle" || color !== void 0) {
|
|
538
|
+
return 0.9;
|
|
539
|
+
}
|
|
540
|
+
return 1;
|
|
541
|
+
}
|
|
542
|
+
function resolveCircleLineWidth(squareSize, lineWidth) {
|
|
543
|
+
const candidate = lineWidth ?? squareSize * 0.08;
|
|
544
|
+
return Math.max(2, Math.min(8, candidate));
|
|
545
|
+
}
|
|
489
546
|
function resolveBorderCoordinateFontSize(context, geometry) {
|
|
490
547
|
const maxFontSize = Math.floor(
|
|
491
548
|
Math.min(geometry.squareSize * 0.6, geometry.borderSize * 0.65)
|
|
@@ -582,6 +639,93 @@ function drawCoordinates(context, request, geometry) {
|
|
|
582
639
|
}
|
|
583
640
|
drawInsideCoordinates(context, request, geometry);
|
|
584
641
|
}
|
|
642
|
+
function drawBoardSquares(context, request, geometry) {
|
|
643
|
+
for (const square of SQUARES) {
|
|
644
|
+
const squareGeometry = geometry.squares[square];
|
|
645
|
+
context.fillStyle = isDarkSquare(square) ? request.colors.darkSquare : request.colors.lightSquare;
|
|
646
|
+
context.fillRect(
|
|
647
|
+
squareGeometry.x,
|
|
648
|
+
squareGeometry.y,
|
|
649
|
+
squareGeometry.size,
|
|
650
|
+
squareGeometry.size
|
|
651
|
+
);
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
function drawFillHighlights(context, request, geometry) {
|
|
655
|
+
for (const highlight of request.highlights) {
|
|
656
|
+
if (highlight.style !== "fill") {
|
|
657
|
+
continue;
|
|
658
|
+
}
|
|
659
|
+
const squareGeometry = geometry.squares[highlight.square];
|
|
660
|
+
context.save();
|
|
661
|
+
context.globalAlpha = resolveHighlightOpacity(
|
|
662
|
+
highlight.style,
|
|
663
|
+
highlight.color,
|
|
664
|
+
highlight.opacity
|
|
665
|
+
);
|
|
666
|
+
context.fillStyle = highlight.color ?? request.colors.highlight;
|
|
667
|
+
context.fillRect(
|
|
668
|
+
squareGeometry.x,
|
|
669
|
+
squareGeometry.y,
|
|
670
|
+
squareGeometry.size,
|
|
671
|
+
squareGeometry.size
|
|
672
|
+
);
|
|
673
|
+
context.restore();
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
function drawCircleHighlights(context, request, geometry) {
|
|
677
|
+
for (const highlight of request.highlights) {
|
|
678
|
+
if (highlight.style !== "circle") {
|
|
679
|
+
continue;
|
|
680
|
+
}
|
|
681
|
+
const squareGeometry = geometry.squares[highlight.square];
|
|
682
|
+
const centerX = squareGeometry.x + squareGeometry.size / 2;
|
|
683
|
+
const centerY = squareGeometry.y + squareGeometry.size / 2;
|
|
684
|
+
context.save();
|
|
685
|
+
context.globalAlpha = resolveHighlightOpacity(
|
|
686
|
+
highlight.style,
|
|
687
|
+
highlight.color,
|
|
688
|
+
highlight.opacity
|
|
689
|
+
);
|
|
690
|
+
context.strokeStyle = highlight.color ?? "#ffcc00";
|
|
691
|
+
context.lineWidth = resolveCircleLineWidth(
|
|
692
|
+
squareGeometry.size,
|
|
693
|
+
highlight.lineWidth
|
|
694
|
+
);
|
|
695
|
+
context.beginPath();
|
|
696
|
+
context.arc(
|
|
697
|
+
centerX,
|
|
698
|
+
centerY,
|
|
699
|
+
squareGeometry.size * 0.32,
|
|
700
|
+
0,
|
|
701
|
+
Math.PI * 2
|
|
702
|
+
);
|
|
703
|
+
context.stroke();
|
|
704
|
+
context.restore();
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
async function drawPieces(context, request, geometry) {
|
|
708
|
+
for (const square of SQUARES) {
|
|
709
|
+
const squareGeometry = geometry.squares[square];
|
|
710
|
+
const pieceKey = request.board.squares[square];
|
|
711
|
+
if (!pieceKey) {
|
|
712
|
+
continue;
|
|
713
|
+
}
|
|
714
|
+
const raster = await getPieceRaster(
|
|
715
|
+
request.theme.name,
|
|
716
|
+
pieceKey,
|
|
717
|
+
request.theme.pieces[pieceKey],
|
|
718
|
+
Math.round(geometry.squareSize)
|
|
719
|
+
);
|
|
720
|
+
context.drawImage(
|
|
721
|
+
raster,
|
|
722
|
+
squareGeometry.x,
|
|
723
|
+
squareGeometry.y,
|
|
724
|
+
squareGeometry.size,
|
|
725
|
+
squareGeometry.size
|
|
726
|
+
);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
585
729
|
var CanvasPngRenderer = class {
|
|
586
730
|
async render(request) {
|
|
587
731
|
try {
|
|
@@ -595,43 +739,11 @@ var CanvasPngRenderer = class {
|
|
|
595
739
|
const context = canvas.getContext("2d");
|
|
596
740
|
context.fillStyle = request.colors.lightSquare;
|
|
597
741
|
context.fillRect(0, 0, geometry.imageWidth, geometry.imageHeight);
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
context.fillRect(
|
|
602
|
-
squareGeometry.x,
|
|
603
|
-
squareGeometry.y,
|
|
604
|
-
squareGeometry.size,
|
|
605
|
-
squareGeometry.size
|
|
606
|
-
);
|
|
607
|
-
if (request.highlights.includes(square)) {
|
|
608
|
-
context.fillStyle = request.colors.highlight;
|
|
609
|
-
context.fillRect(
|
|
610
|
-
squareGeometry.x,
|
|
611
|
-
squareGeometry.y,
|
|
612
|
-
squareGeometry.size,
|
|
613
|
-
squareGeometry.size
|
|
614
|
-
);
|
|
615
|
-
}
|
|
616
|
-
const pieceKey = request.board.squares[square];
|
|
617
|
-
if (!pieceKey) {
|
|
618
|
-
continue;
|
|
619
|
-
}
|
|
620
|
-
const raster = await getPieceRaster(
|
|
621
|
-
request.theme.name,
|
|
622
|
-
pieceKey,
|
|
623
|
-
request.theme.pieces[pieceKey],
|
|
624
|
-
Math.round(geometry.squareSize)
|
|
625
|
-
);
|
|
626
|
-
context.drawImage(
|
|
627
|
-
raster,
|
|
628
|
-
squareGeometry.x,
|
|
629
|
-
squareGeometry.y,
|
|
630
|
-
squareGeometry.size,
|
|
631
|
-
squareGeometry.size
|
|
632
|
-
);
|
|
633
|
-
}
|
|
742
|
+
drawBoardSquares(context, request, geometry);
|
|
743
|
+
drawFillHighlights(context, request, geometry);
|
|
744
|
+
drawCircleHighlights(context, request, geometry);
|
|
634
745
|
drawCoordinates(context, request, geometry);
|
|
746
|
+
await drawPieces(context, request, geometry);
|
|
635
747
|
return canvas.toBuffer("image/png");
|
|
636
748
|
} catch (error) {
|
|
637
749
|
if (error instanceof RenderError) {
|
|
@@ -652,6 +764,26 @@ async function writeBufferToFile(filePath, buffer) {
|
|
|
652
764
|
}
|
|
653
765
|
}
|
|
654
766
|
|
|
767
|
+
// src/core/highlights.ts
|
|
768
|
+
function normalizeHighlightEntries(input) {
|
|
769
|
+
return input.map((entry) => {
|
|
770
|
+
if (typeof entry === "string") {
|
|
771
|
+
return {
|
|
772
|
+
square: validateSquare(entry),
|
|
773
|
+
style: "fill"
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
const style = entry.style ?? "fill";
|
|
777
|
+
return {
|
|
778
|
+
square: validateSquare(entry.square),
|
|
779
|
+
style,
|
|
780
|
+
color: entry.color ?? (style === "circle" ? "#ffcc00" : void 0),
|
|
781
|
+
opacity: entry.opacity ?? (style === "circle" ? 0.9 : void 0),
|
|
782
|
+
lineWidth: entry.lineWidth
|
|
783
|
+
};
|
|
784
|
+
});
|
|
785
|
+
}
|
|
786
|
+
|
|
655
787
|
// src/themes/builtins.ts
|
|
656
788
|
import { existsSync } from "fs";
|
|
657
789
|
import { resolve } from "path";
|
|
@@ -837,6 +969,9 @@ function normalizeCoordinates(coordinates, borderSize) {
|
|
|
837
969
|
color: coordinates.color ?? (position === "border" ? "#333" : void 0)
|
|
838
970
|
};
|
|
839
971
|
}
|
|
972
|
+
function normalizeHighlightEntries2(highlights) {
|
|
973
|
+
return normalizeHighlightEntries(highlights ?? []);
|
|
974
|
+
}
|
|
840
975
|
function normalizeRenderInputs(options) {
|
|
841
976
|
const size = validateSize(options.size ?? DEFAULT_SIZE);
|
|
842
977
|
const borderSize = validateBorderSize(
|
|
@@ -845,6 +980,8 @@ function normalizeRenderInputs(options) {
|
|
|
845
980
|
);
|
|
846
981
|
validateBoardColors(options.colors);
|
|
847
982
|
validateCoordinatesOption(options.coordinates, borderSize);
|
|
983
|
+
validateHighlightsInput(options.highlights, options.highlightSquares);
|
|
984
|
+
const highlightInput = options.highlights ?? options.highlightSquares;
|
|
848
985
|
return {
|
|
849
986
|
size,
|
|
850
987
|
padding: normalizePadding(options.padding ?? DEFAULT_PADDING),
|
|
@@ -854,7 +991,7 @@ function normalizeRenderInputs(options) {
|
|
|
854
991
|
theme: options.theme,
|
|
855
992
|
style: options.style
|
|
856
993
|
}),
|
|
857
|
-
|
|
994
|
+
highlights: normalizeHighlightEntries2(highlightInput),
|
|
858
995
|
colors: normalizeColors(options.colors),
|
|
859
996
|
coordinates: normalizeCoordinates(options.coordinates, borderSize)
|
|
860
997
|
};
|
|
@@ -867,10 +1004,7 @@ var ChessImageGenerator = class {
|
|
|
867
1004
|
highlights = [];
|
|
868
1005
|
constructor(options = {}) {
|
|
869
1006
|
this.defaults = { ...options };
|
|
870
|
-
normalizeRenderInputs(
|
|
871
|
-
...this.defaults,
|
|
872
|
-
highlightSquares: []
|
|
873
|
-
});
|
|
1007
|
+
normalizeRenderInputs(this.defaults);
|
|
874
1008
|
}
|
|
875
1009
|
async loadFEN(fen) {
|
|
876
1010
|
this.position = parseFEN(fen);
|
|
@@ -884,8 +1018,9 @@ var ChessImageGenerator = class {
|
|
|
884
1018
|
this.position = parseBoardArray(board);
|
|
885
1019
|
this.clearHighlights();
|
|
886
1020
|
}
|
|
887
|
-
setHighlights(
|
|
888
|
-
|
|
1021
|
+
setHighlights(highlights) {
|
|
1022
|
+
validateHighlightsInput(highlights, void 0);
|
|
1023
|
+
this.highlights = [...highlights];
|
|
889
1024
|
}
|
|
890
1025
|
clearHighlights() {
|
|
891
1026
|
this.highlights = [];
|
|
@@ -897,12 +1032,13 @@ var ChessImageGenerator = class {
|
|
|
897
1032
|
const renderer = new CanvasPngRenderer();
|
|
898
1033
|
const normalized = normalizeRenderInputs({
|
|
899
1034
|
...this.defaults,
|
|
900
|
-
|
|
1035
|
+
highlights: this.highlights,
|
|
1036
|
+
highlightSquares: void 0
|
|
901
1037
|
});
|
|
902
1038
|
return renderer.render({
|
|
903
1039
|
board: this.position,
|
|
904
1040
|
theme: normalized.theme,
|
|
905
|
-
highlights: normalized.
|
|
1041
|
+
highlights: normalized.highlights,
|
|
906
1042
|
size: normalized.size,
|
|
907
1043
|
padding: normalized.padding,
|
|
908
1044
|
borderSize: normalized.borderSize,
|
|
@@ -945,7 +1081,7 @@ async function renderChess(options) {
|
|
|
945
1081
|
return renderer.render({
|
|
946
1082
|
board: position,
|
|
947
1083
|
theme: normalized.theme,
|
|
948
|
-
highlights: normalized.
|
|
1084
|
+
highlights: normalized.highlights,
|
|
949
1085
|
size: normalized.size,
|
|
950
1086
|
padding: normalized.padding,
|
|
951
1087
|
borderSize: normalized.borderSize,
|