chess2img 0.2.2 → 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.
- package/README.md +38 -3
- package/dist/index.cjs +204 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -4
- package/dist/index.d.ts +23 -4
- package/dist/index.js +204 -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,52 @@ 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
|
+
if (entry.radius !== void 0 && (!Number.isFinite(entry.radius) || entry.radius <= 0 || entry.radius > 0.5)) {
|
|
227
|
+
throw new ValidationError("highlight.radius must be a finite number greater than 0 and at most 0.5");
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
function validateHighlightOptions(highlights) {
|
|
231
|
+
if (highlights === void 0) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
if (!Array.isArray(highlights)) {
|
|
235
|
+
throw new ValidationError("highlights must be an array");
|
|
236
|
+
}
|
|
237
|
+
for (const entry of highlights) {
|
|
238
|
+
validateHighlightEntry(entry);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function validateHighlightsInput(highlights, highlightSquares) {
|
|
242
|
+
if (highlights !== void 0 && highlightSquares !== void 0) {
|
|
243
|
+
throw new ValidationError("Use either highlights or highlightSquares, not both");
|
|
244
|
+
}
|
|
245
|
+
validateHighlightOptions(highlights);
|
|
246
|
+
validateHighlightOptions(highlightSquares);
|
|
247
|
+
}
|
|
196
248
|
|
|
197
249
|
// src/core/parsers.ts
|
|
198
250
|
var PIECE_SYMBOL_TO_KEY = {
|
|
@@ -256,11 +308,6 @@ function parseBoardArray(board) {
|
|
|
256
308
|
return position;
|
|
257
309
|
}
|
|
258
310
|
|
|
259
|
-
// src/core/highlights.ts
|
|
260
|
-
function normalizeHighlights(input) {
|
|
261
|
-
return [...new Set(input.map(validateSquare))].sort();
|
|
262
|
-
}
|
|
263
|
-
|
|
264
311
|
// src/render/canvas-renderer.ts
|
|
265
312
|
import { createCanvas as createCanvas3 } from "canvas";
|
|
266
313
|
|
|
@@ -486,6 +533,23 @@ function resolveInsideLabelColor(request, square) {
|
|
|
486
533
|
}
|
|
487
534
|
return isDarkSquare(square) ? INSIDE_LIGHT_LABEL_COLOR : INSIDE_DARK_LABEL_COLOR;
|
|
488
535
|
}
|
|
536
|
+
function resolveHighlightOpacity(style, color, opacity) {
|
|
537
|
+
if (opacity !== void 0) {
|
|
538
|
+
return opacity;
|
|
539
|
+
}
|
|
540
|
+
if (style === "circle" || color !== void 0) {
|
|
541
|
+
return 0.9;
|
|
542
|
+
}
|
|
543
|
+
return 1;
|
|
544
|
+
}
|
|
545
|
+
function resolveCircleLineWidth(squareSize, lineWidth) {
|
|
546
|
+
const candidate = lineWidth ?? squareSize * 0.08;
|
|
547
|
+
return Math.max(2, Math.min(8, candidate));
|
|
548
|
+
}
|
|
549
|
+
function resolveCircleRadius(squareSize, radius, lineWidth) {
|
|
550
|
+
const radiusPx = squareSize * (radius ?? 0.42);
|
|
551
|
+
return Math.max(0, radiusPx - lineWidth / 2);
|
|
552
|
+
}
|
|
489
553
|
function resolveBorderCoordinateFontSize(context, geometry) {
|
|
490
554
|
const maxFontSize = Math.floor(
|
|
491
555
|
Math.min(geometry.squareSize * 0.6, geometry.borderSize * 0.65)
|
|
@@ -582,6 +646,98 @@ function drawCoordinates(context, request, geometry) {
|
|
|
582
646
|
}
|
|
583
647
|
drawInsideCoordinates(context, request, geometry);
|
|
584
648
|
}
|
|
649
|
+
function drawBoardSquares(context, request, geometry) {
|
|
650
|
+
for (const square of SQUARES) {
|
|
651
|
+
const squareGeometry = geometry.squares[square];
|
|
652
|
+
context.fillStyle = isDarkSquare(square) ? request.colors.darkSquare : request.colors.lightSquare;
|
|
653
|
+
context.fillRect(
|
|
654
|
+
squareGeometry.x,
|
|
655
|
+
squareGeometry.y,
|
|
656
|
+
squareGeometry.size,
|
|
657
|
+
squareGeometry.size
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
function drawFillHighlights(context, request, geometry) {
|
|
662
|
+
for (const highlight of request.highlights) {
|
|
663
|
+
if (highlight.style !== "fill") {
|
|
664
|
+
continue;
|
|
665
|
+
}
|
|
666
|
+
const squareGeometry = geometry.squares[highlight.square];
|
|
667
|
+
context.save();
|
|
668
|
+
context.globalAlpha = resolveHighlightOpacity(
|
|
669
|
+
highlight.style,
|
|
670
|
+
highlight.color,
|
|
671
|
+
highlight.opacity
|
|
672
|
+
);
|
|
673
|
+
context.fillStyle = highlight.color ?? request.colors.highlight;
|
|
674
|
+
context.fillRect(
|
|
675
|
+
squareGeometry.x,
|
|
676
|
+
squareGeometry.y,
|
|
677
|
+
squareGeometry.size,
|
|
678
|
+
squareGeometry.size
|
|
679
|
+
);
|
|
680
|
+
context.restore();
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
function drawCircleHighlights(context, request, geometry) {
|
|
684
|
+
for (const highlight of request.highlights) {
|
|
685
|
+
if (highlight.style !== "circle") {
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
const squareGeometry = geometry.squares[highlight.square];
|
|
689
|
+
const centerX = squareGeometry.x + squareGeometry.size / 2;
|
|
690
|
+
const centerY = squareGeometry.y + squareGeometry.size / 2;
|
|
691
|
+
context.save();
|
|
692
|
+
context.globalAlpha = resolveHighlightOpacity(
|
|
693
|
+
highlight.style,
|
|
694
|
+
highlight.color,
|
|
695
|
+
highlight.opacity
|
|
696
|
+
);
|
|
697
|
+
context.strokeStyle = highlight.color ?? "#ffcc00";
|
|
698
|
+
context.lineWidth = resolveCircleLineWidth(
|
|
699
|
+
squareGeometry.size,
|
|
700
|
+
highlight.lineWidth
|
|
701
|
+
);
|
|
702
|
+
const radius = resolveCircleRadius(
|
|
703
|
+
squareGeometry.size,
|
|
704
|
+
highlight.radius,
|
|
705
|
+
context.lineWidth
|
|
706
|
+
);
|
|
707
|
+
context.beginPath();
|
|
708
|
+
context.arc(
|
|
709
|
+
centerX,
|
|
710
|
+
centerY,
|
|
711
|
+
radius,
|
|
712
|
+
0,
|
|
713
|
+
Math.PI * 2
|
|
714
|
+
);
|
|
715
|
+
context.stroke();
|
|
716
|
+
context.restore();
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
async function drawPieces(context, request, geometry) {
|
|
720
|
+
for (const square of SQUARES) {
|
|
721
|
+
const squareGeometry = geometry.squares[square];
|
|
722
|
+
const pieceKey = request.board.squares[square];
|
|
723
|
+
if (!pieceKey) {
|
|
724
|
+
continue;
|
|
725
|
+
}
|
|
726
|
+
const raster = await getPieceRaster(
|
|
727
|
+
request.theme.name,
|
|
728
|
+
pieceKey,
|
|
729
|
+
request.theme.pieces[pieceKey],
|
|
730
|
+
Math.round(geometry.squareSize)
|
|
731
|
+
);
|
|
732
|
+
context.drawImage(
|
|
733
|
+
raster,
|
|
734
|
+
squareGeometry.x,
|
|
735
|
+
squareGeometry.y,
|
|
736
|
+
squareGeometry.size,
|
|
737
|
+
squareGeometry.size
|
|
738
|
+
);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
585
741
|
var CanvasPngRenderer = class {
|
|
586
742
|
async render(request) {
|
|
587
743
|
try {
|
|
@@ -595,43 +751,11 @@ var CanvasPngRenderer = class {
|
|
|
595
751
|
const context = canvas.getContext("2d");
|
|
596
752
|
context.fillStyle = request.colors.lightSquare;
|
|
597
753
|
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
|
-
}
|
|
754
|
+
drawBoardSquares(context, request, geometry);
|
|
755
|
+
drawFillHighlights(context, request, geometry);
|
|
756
|
+
drawCircleHighlights(context, request, geometry);
|
|
634
757
|
drawCoordinates(context, request, geometry);
|
|
758
|
+
await drawPieces(context, request, geometry);
|
|
635
759
|
return canvas.toBuffer("image/png");
|
|
636
760
|
} catch (error) {
|
|
637
761
|
if (error instanceof RenderError) {
|
|
@@ -652,6 +776,31 @@ async function writeBufferToFile(filePath, buffer) {
|
|
|
652
776
|
}
|
|
653
777
|
}
|
|
654
778
|
|
|
779
|
+
// src/core/highlights.ts
|
|
780
|
+
function normalizeHighlightEntries(input) {
|
|
781
|
+
return input.map((entry) => {
|
|
782
|
+
if (typeof entry === "string") {
|
|
783
|
+
return {
|
|
784
|
+
square: validateSquare(entry),
|
|
785
|
+
style: "fill",
|
|
786
|
+
color: void 0,
|
|
787
|
+
opacity: void 0,
|
|
788
|
+
lineWidth: void 0,
|
|
789
|
+
radius: void 0
|
|
790
|
+
};
|
|
791
|
+
}
|
|
792
|
+
const style = entry.style ?? "fill";
|
|
793
|
+
return {
|
|
794
|
+
square: validateSquare(entry.square),
|
|
795
|
+
style,
|
|
796
|
+
color: entry.color ?? (style === "circle" ? "#ffcc00" : void 0),
|
|
797
|
+
opacity: entry.opacity ?? (style === "circle" ? 0.9 : void 0),
|
|
798
|
+
lineWidth: entry.lineWidth,
|
|
799
|
+
radius: style === "circle" ? entry.radius ?? 0.42 : void 0
|
|
800
|
+
};
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
|
|
655
804
|
// src/themes/builtins.ts
|
|
656
805
|
import { existsSync } from "fs";
|
|
657
806
|
import { resolve } from "path";
|
|
@@ -837,6 +986,9 @@ function normalizeCoordinates(coordinates, borderSize) {
|
|
|
837
986
|
color: coordinates.color ?? (position === "border" ? "#333" : void 0)
|
|
838
987
|
};
|
|
839
988
|
}
|
|
989
|
+
function normalizeHighlightEntries2(highlights) {
|
|
990
|
+
return normalizeHighlightEntries(highlights ?? []);
|
|
991
|
+
}
|
|
840
992
|
function normalizeRenderInputs(options) {
|
|
841
993
|
const size = validateSize(options.size ?? DEFAULT_SIZE);
|
|
842
994
|
const borderSize = validateBorderSize(
|
|
@@ -845,6 +997,8 @@ function normalizeRenderInputs(options) {
|
|
|
845
997
|
);
|
|
846
998
|
validateBoardColors(options.colors);
|
|
847
999
|
validateCoordinatesOption(options.coordinates, borderSize);
|
|
1000
|
+
validateHighlightsInput(options.highlights, options.highlightSquares);
|
|
1001
|
+
const highlightInput = options.highlights ?? options.highlightSquares;
|
|
848
1002
|
return {
|
|
849
1003
|
size,
|
|
850
1004
|
padding: normalizePadding(options.padding ?? DEFAULT_PADDING),
|
|
@@ -854,7 +1008,7 @@ function normalizeRenderInputs(options) {
|
|
|
854
1008
|
theme: options.theme,
|
|
855
1009
|
style: options.style
|
|
856
1010
|
}),
|
|
857
|
-
|
|
1011
|
+
highlights: normalizeHighlightEntries2(highlightInput),
|
|
858
1012
|
colors: normalizeColors(options.colors),
|
|
859
1013
|
coordinates: normalizeCoordinates(options.coordinates, borderSize)
|
|
860
1014
|
};
|
|
@@ -867,10 +1021,7 @@ var ChessImageGenerator = class {
|
|
|
867
1021
|
highlights = [];
|
|
868
1022
|
constructor(options = {}) {
|
|
869
1023
|
this.defaults = { ...options };
|
|
870
|
-
normalizeRenderInputs(
|
|
871
|
-
...this.defaults,
|
|
872
|
-
highlightSquares: []
|
|
873
|
-
});
|
|
1024
|
+
normalizeRenderInputs(this.defaults);
|
|
874
1025
|
}
|
|
875
1026
|
async loadFEN(fen) {
|
|
876
1027
|
this.position = parseFEN(fen);
|
|
@@ -884,8 +1035,9 @@ var ChessImageGenerator = class {
|
|
|
884
1035
|
this.position = parseBoardArray(board);
|
|
885
1036
|
this.clearHighlights();
|
|
886
1037
|
}
|
|
887
|
-
setHighlights(
|
|
888
|
-
|
|
1038
|
+
setHighlights(highlights) {
|
|
1039
|
+
validateHighlightsInput(highlights, void 0);
|
|
1040
|
+
this.highlights = [...highlights];
|
|
889
1041
|
}
|
|
890
1042
|
clearHighlights() {
|
|
891
1043
|
this.highlights = [];
|
|
@@ -897,12 +1049,13 @@ var ChessImageGenerator = class {
|
|
|
897
1049
|
const renderer = new CanvasPngRenderer();
|
|
898
1050
|
const normalized = normalizeRenderInputs({
|
|
899
1051
|
...this.defaults,
|
|
900
|
-
|
|
1052
|
+
highlights: this.highlights,
|
|
1053
|
+
highlightSquares: void 0
|
|
901
1054
|
});
|
|
902
1055
|
return renderer.render({
|
|
903
1056
|
board: this.position,
|
|
904
1057
|
theme: normalized.theme,
|
|
905
|
-
highlights: normalized.
|
|
1058
|
+
highlights: normalized.highlights,
|
|
906
1059
|
size: normalized.size,
|
|
907
1060
|
padding: normalized.padding,
|
|
908
1061
|
borderSize: normalized.borderSize,
|
|
@@ -945,7 +1098,7 @@ async function renderChess(options) {
|
|
|
945
1098
|
return renderer.render({
|
|
946
1099
|
board: position,
|
|
947
1100
|
theme: normalized.theme,
|
|
948
|
-
highlights: normalized.
|
|
1101
|
+
highlights: normalized.highlights,
|
|
949
1102
|
size: normalized.size,
|
|
950
1103
|
padding: normalized.padding,
|
|
951
1104
|
borderSize: normalized.borderSize,
|