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/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
- for (const square of SQUARES) {
599
- const squareGeometry = geometry.squares[square];
600
- context.fillStyle = isDarkSquare(square) ? request.colors.darkSquare : request.colors.lightSquare;
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
- highlightSquares: normalizeHighlights(options.highlightSquares ?? []),
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(squares) {
888
- this.highlights = normalizeHighlights(squares);
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
- highlightSquares: this.highlights
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.highlightSquares,
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.highlightSquares,
1101
+ highlights: normalized.highlights,
949
1102
  size: normalized.size,
950
1103
  padding: normalized.padding,
951
1104
  borderSize: normalized.borderSize,