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/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
- 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
- }
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
- highlightSquares: normalizeHighlights(options.highlightSquares ?? []),
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(squares) {
888
- this.highlights = normalizeHighlights(squares);
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
- highlightSquares: this.highlights
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.highlightSquares,
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.highlightSquares,
1084
+ highlights: normalized.highlights,
949
1085
  size: normalized.size,
950
1086
  padding: normalized.padding,
951
1087
  borderSize: normalized.borderSize,