chess2img 0.1.4 → 0.2.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 CHANGED
@@ -20,7 +20,7 @@
20
20
  - Render PNG chessboard images from `fen`, `pgn`, or `board` input.
21
21
  - Use five bundled built-in themes: `merida`, `alpha`, `cburnett`, `cheq`, and `leipzig`.
22
22
  - Highlight squares such as the last move or key tactical ideas.
23
- - Customize board colors, size, padding, and board orientation.
23
+ - Customize board colors, size, padding, border sizing, coordinates, and board orientation.
24
24
  - Use either the functional `renderChess(...)` API or the `ChessImageGenerator` class API.
25
25
  - Register custom themes globally or pass a theme inline for one-off rendering.
26
26
  - Consume the package from both JavaScript and TypeScript projects.
@@ -89,6 +89,61 @@ const png = await renderChess({
89
89
  await writeFile("board.png", png);
90
90
  ```
91
91
 
92
+ ### Automatic Coordinates
93
+
94
+ ```ts
95
+ import { writeFile } from "node:fs/promises";
96
+ import { renderChess } from "chess2img";
97
+
98
+ const png = await renderChess({
99
+ fen: "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1",
100
+ size: 480,
101
+ style: "cburnett",
102
+ coordinates: true,
103
+ });
104
+
105
+ await writeFile("board-with-auto-coordinates.png", png);
106
+ ```
107
+
108
+ `coordinates: true` chooses `border` mode when `borderSize > 0`, otherwise it falls back to `inside` mode.
109
+
110
+ ### Border Coordinates
111
+
112
+ ```ts
113
+ import { writeFile } from "node:fs/promises";
114
+ import { renderChess } from "chess2img";
115
+
116
+ const png = await renderChess({
117
+ fen: "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1",
118
+ size: 480,
119
+ style: "cburnett",
120
+ borderSize: 24,
121
+ coordinates: {
122
+ enabled: true,
123
+ position: "border",
124
+ color: "#333",
125
+ },
126
+ });
127
+
128
+ await writeFile("board-with-border-coordinates.png", png);
129
+ ```
130
+
131
+ ### Inside Coordinates
132
+
133
+ ```ts
134
+ import { writeFile } from "node:fs/promises";
135
+ import { renderChess } from "chess2img";
136
+
137
+ const png = await renderChess({
138
+ fen: "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1",
139
+ size: 480,
140
+ style: "cburnett",
141
+ coordinates: "inside",
142
+ });
143
+
144
+ await writeFile("board-with-inside-coordinates.png", png);
145
+ ```
146
+
92
147
  ### Class API
93
148
 
94
149
  ```ts
@@ -202,14 +257,22 @@ Semantics:
202
257
 
203
258
  - `size`: board size in pixels
204
259
  - `padding`: `[top, right, bottom, left]`
260
+ - `borderSize`: inner border size in pixels, from `0` up to `Math.floor(size / 8)`
205
261
  - `flipped`: render from black's perspective when `true`
206
262
  - `style`: built-in theme alias
207
263
  - `theme`: built-in theme name, registered custom theme name, or inline `ThemeDefinition`
208
264
  - `highlightSquares`: array of algebraic squares such as `["e4", "d5"]`
265
+ - `coordinates`: `boolean`, `"border"`, `"inside"`, or `{ enabled?: boolean; position?: "border" | "inside"; color?: string }`
209
266
  - `colors.lightSquare`
210
267
  - `colors.darkSquare`
211
268
  - `colors.highlight`
212
269
 
270
+ `coordinates: false` or omitting the option disables labels. `coordinates: true` enables labels and chooses `border` mode when `borderSize > 0`, otherwise `inside` mode. Explicit `coordinates: "inside"` is always valid. Explicit `coordinates: "border"` requires `borderSize > 0` and throws `ValidationError` otherwise.
271
+
272
+ Inside coordinates use automatic light/dark contrast by default, similar to chess.com. If `coordinates.color` is provided, that exact color is used instead. Border coordinates keep a single-color label style with `#333` as the default.
273
+
274
+ At very small valid sizes, the renderer suppresses coordinates when they cannot fit legibly in the available border band or edge-square area.
275
+
213
276
  ### Errors
214
277
 
215
278
  The library exports:
package/dist/index.cjs CHANGED
@@ -81,8 +81,10 @@ function createEmptyBoardPosition() {
81
81
  }
82
82
 
83
83
  // src/core/validators.ts
84
+ var import_canvas = require("canvas");
84
85
  var SQUARE_PATTERN = /^[a-h][1-8]$/;
85
86
  var PIECE_PATTERN = /^[prnbqkPRNBQK]$/;
87
+ var colorValidationContext = (0, import_canvas.createCanvas)(1, 1).getContext("2d");
86
88
  function validateSize(size) {
87
89
  if (!Number.isFinite(size) || size <= 0) {
88
90
  throw new ValidationError(`Invalid board size: ${size}`);
@@ -135,6 +137,88 @@ function validateThemeName(name) {
135
137
  }
136
138
  return normalized;
137
139
  }
140
+ function validateBorderSize(borderSize, size) {
141
+ const normalizedSize = validateSize(size);
142
+ const normalizedBorderSize = Math.round(borderSize);
143
+ const maxBorderSize = Math.floor(normalizedSize / 8);
144
+ if (!Number.isFinite(borderSize) || normalizedBorderSize < 0) {
145
+ throw new ValidationError(`Invalid borderSize: ${borderSize}`);
146
+ }
147
+ if (normalizedBorderSize > maxBorderSize) {
148
+ throw new ValidationError(
149
+ `Invalid borderSize: ${borderSize}. Maximum allowed for size ${normalizedSize} is ${maxBorderSize}`
150
+ );
151
+ }
152
+ return normalizedBorderSize;
153
+ }
154
+ function validateColorString(color, label = "color") {
155
+ if (typeof color !== "string" || color.trim() === "") {
156
+ throw new ValidationError(`Invalid ${label}: ${String(color)}`);
157
+ }
158
+ const normalized = color.trim();
159
+ colorValidationContext.fillStyle = "#010203";
160
+ colorValidationContext.fillStyle = normalized;
161
+ const firstPass = String(colorValidationContext.fillStyle);
162
+ colorValidationContext.fillStyle = "#fefefe";
163
+ colorValidationContext.fillStyle = normalized;
164
+ const secondPass = String(colorValidationContext.fillStyle);
165
+ if (firstPass !== secondPass) {
166
+ throw new ValidationError(`Invalid ${label}: ${color}`);
167
+ }
168
+ return normalized;
169
+ }
170
+ function validateBoardColors(colors) {
171
+ if (!colors) {
172
+ return;
173
+ }
174
+ if (colors.lightSquare !== void 0) {
175
+ validateColorString(colors.lightSquare, "lightSquare color");
176
+ }
177
+ if (colors.darkSquare !== void 0) {
178
+ validateColorString(colors.darkSquare, "darkSquare color");
179
+ }
180
+ if (colors.highlight !== void 0) {
181
+ validateColorString(colors.highlight, "highlight color");
182
+ }
183
+ }
184
+ function isCoordinatesOptions(value) {
185
+ return typeof value === "object" && value !== null && !Array.isArray(value);
186
+ }
187
+ function isCoordinatesPosition(value) {
188
+ return value === "border" || value === "inside";
189
+ }
190
+ function validateCoordinatesOption(coordinates, borderSize) {
191
+ if (coordinates === void 0 || typeof coordinates === "boolean") {
192
+ return;
193
+ }
194
+ if (isCoordinatesPosition(coordinates)) {
195
+ if (coordinates === "border" && borderSize === 0) {
196
+ throw new ValidationError(
197
+ "coordinates position 'border' requires borderSize > 0"
198
+ );
199
+ }
200
+ return;
201
+ }
202
+ if (!isCoordinatesOptions(coordinates)) {
203
+ throw new ValidationError(
204
+ "coordinates must be false, true, 'border', 'inside', or an options object"
205
+ );
206
+ }
207
+ if (coordinates.enabled !== void 0 && typeof coordinates.enabled !== "boolean") {
208
+ throw new ValidationError("coordinates.enabled must be a boolean");
209
+ }
210
+ if (coordinates.position !== void 0 && !isCoordinatesPosition(coordinates.position)) {
211
+ throw new ValidationError("coordinates.position must be 'border' or 'inside'");
212
+ }
213
+ if (coordinates.enabled !== false && coordinates.position === "border" && borderSize === 0) {
214
+ throw new ValidationError(
215
+ "coordinates position 'border' requires borderSize > 0"
216
+ );
217
+ }
218
+ if (coordinates.color !== void 0) {
219
+ validateColorString(coordinates.color, "coordinates.color");
220
+ }
221
+ }
138
222
 
139
223
  // src/core/parsers.ts
140
224
  var PIECE_SYMBOL_TO_KEY = {
@@ -204,32 +288,103 @@ function normalizeHighlights(input) {
204
288
  }
205
289
 
206
290
  // src/render/canvas-renderer.ts
207
- var import_canvas2 = require("canvas");
291
+ var import_canvas3 = require("canvas");
208
292
 
209
293
  // src/core/geometry.ts
210
294
  function createBoardGeometry({
211
295
  size,
212
296
  padding,
297
+ borderSize,
213
298
  flipped
214
299
  }) {
215
300
  const [top, right, bottom, left] = padding;
216
- const squareSize = size / 8;
301
+ const boardOuterSize = size;
302
+ const boardOuterX = left;
303
+ const boardOuterY = top;
304
+ const boardX = left + borderSize;
305
+ const boardY = top + borderSize;
306
+ const boardSize = size - borderSize * 2;
307
+ const squareSize = boardSize / 8;
217
308
  const squares = Object.fromEntries(
218
309
  SQUARES.map((square, index) => {
219
310
  const fileIndex = index % 8;
220
311
  const rankIndex = Math.floor(index / 8);
221
- const x = left + (flipped ? 7 - fileIndex : fileIndex) * squareSize;
222
- const y = top + (flipped ? 7 - rankIndex : rankIndex) * squareSize;
312
+ const x = boardX + (flipped ? 7 - fileIndex : fileIndex) * squareSize;
313
+ const y = boardY + (flipped ? 7 - rankIndex : rankIndex) * squareSize;
223
314
  return [square, { x, y, size: squareSize }];
224
315
  })
225
316
  );
317
+ const displayedFiles = flipped ? [...FILES].reverse() : [...FILES];
318
+ const displayedRanks = flipped ? [...RANKS].reverse() : [...RANKS];
319
+ const bottomEdgeRank = flipped ? "8" : "1";
320
+ const leftEdgeFile = flipped ? "h" : "a";
321
+ const borderFileLabels = borderSize ? displayedFiles.map((file, fileIndex) => ({
322
+ text: file,
323
+ x: boardX + fileIndex * squareSize + squareSize / 2,
324
+ y: boardOuterY + boardOuterSize - borderSize / 2,
325
+ square: `${file}${bottomEdgeRank}`,
326
+ textAlign: "center",
327
+ textBaseline: "middle"
328
+ })) : [];
329
+ const borderRankLabels = borderSize ? displayedRanks.map((rank, rankIndex) => ({
330
+ text: rank,
331
+ x: boardOuterX + borderSize / 2,
332
+ y: boardY + rankIndex * squareSize + squareSize / 2,
333
+ square: `${leftEdgeFile}${rank}`,
334
+ textAlign: "center",
335
+ textBaseline: "middle"
336
+ })) : [];
337
+ const insideFileInsetX = squareSize * 0.14;
338
+ const insideFileInsetY = squareSize * 0.1;
339
+ const insideRankInsetX = squareSize * 0.14;
340
+ const insideRankInsetY = squareSize * 0.1;
341
+ const insideLabelMaxWidth = squareSize * 0.26;
342
+ const insideLabelMaxHeight = squareSize * 0.24;
343
+ const insideFileLabels = displayedFiles.map((file) => {
344
+ const square = `${file}${bottomEdgeRank}`;
345
+ const squareGeometry = squares[square];
346
+ return {
347
+ text: file,
348
+ x: squareGeometry.x + insideFileInsetX,
349
+ y: squareGeometry.y + squareGeometry.size - insideFileInsetY,
350
+ square,
351
+ textAlign: "left",
352
+ textBaseline: "bottom"
353
+ };
354
+ });
355
+ const insideRankLabels = displayedRanks.map((rank) => {
356
+ const square = `${leftEdgeFile}${rank}`;
357
+ const squareGeometry = squares[square];
358
+ return {
359
+ text: rank,
360
+ x: squareGeometry.x + insideRankInsetX,
361
+ y: squareGeometry.y + insideRankInsetY,
362
+ square,
363
+ textAlign: "left",
364
+ textBaseline: "top"
365
+ };
366
+ });
226
367
  return {
227
368
  imageWidth: left + size + right,
228
369
  imageHeight: top + size + bottom,
229
370
  squareSize,
230
- boardX: left,
231
- boardY: top,
232
- boardSize: size,
371
+ borderSize,
372
+ boardOuterX,
373
+ boardOuterY,
374
+ boardOuterSize,
375
+ boardX,
376
+ boardY,
377
+ boardSize,
378
+ borderFileLabels,
379
+ borderRankLabels,
380
+ insideFileLabels,
381
+ insideRankLabels,
382
+ insideFileInsetX,
383
+ insideFileInsetY,
384
+ insideRankInsetX,
385
+ insideRankInsetY,
386
+ insideLabelMaxWidth,
387
+ insideLabelMaxHeight,
233
388
  squares
234
389
  };
235
390
  }
@@ -259,7 +414,7 @@ var SourceAssetCache = class {
259
414
 
260
415
  // src/render/rasterizer.ts
261
416
  var import_promises = require("fs/promises");
262
- var import_canvas = require("canvas");
417
+ var import_canvas2 = require("canvas");
263
418
  var import_resvg_js = require("@resvg/resvg-js");
264
419
  var svgSourceCache = new SourceAssetCache();
265
420
  var imageBufferCache = new SourceAssetCache();
@@ -299,8 +454,8 @@ async function rasterizeSvgAsset(filePath, squareSize) {
299
454
  }
300
455
  });
301
456
  const pngBuffer = resvg.render().asPng();
302
- const image = await (0, import_canvas.loadImage)(pngBuffer);
303
- const canvas = (0, import_canvas.createCanvas)(squareSize, squareSize);
457
+ const image = await (0, import_canvas2.loadImage)(pngBuffer);
458
+ const canvas = (0, import_canvas2.createCanvas)(squareSize, squareSize);
304
459
  const context = canvas.getContext("2d");
305
460
  context.drawImage(image, 0, 0, squareSize, squareSize);
306
461
  return canvas;
@@ -311,8 +466,8 @@ async function rasterizeSvgAsset(filePath, squareSize) {
311
466
  async function rasterizePngAsset(filePath, squareSize) {
312
467
  try {
313
468
  const pngSource = await readBinaryAsset(filePath);
314
- const image = await (0, import_canvas.loadImage)(pngSource);
315
- const canvas = (0, import_canvas.createCanvas)(squareSize, squareSize);
469
+ const image = await (0, import_canvas2.loadImage)(pngSource);
470
+ const canvas = (0, import_canvas2.createCanvas)(squareSize, squareSize);
316
471
  const context = canvas.getContext("2d");
317
472
  context.drawImage(image, 0, 0, squareSize, squareSize);
318
473
  return canvas;
@@ -329,6 +484,13 @@ async function rasterizeThemeAsset(asset, squareSize) {
329
484
 
330
485
  // src/render/canvas-renderer.ts
331
486
  var pieceRasterCache = new RasterAssetCache();
487
+ var MIN_COORDINATE_FONT_SIZE = 8;
488
+ var MAX_FILE_LABEL_WIDTH_RATIO = 0.75;
489
+ var MAX_RANK_LABEL_WIDTH_RATIO = 0.7;
490
+ var MAX_LABEL_HEIGHT_RATIO = 0.7;
491
+ var INSIDE_COORDINATE_MAX_FONT_RATIO = 0.34;
492
+ var INSIDE_LIGHT_LABEL_COLOR = "rgba(255,255,255,0.6)";
493
+ var INSIDE_DARK_LABEL_COLOR = "rgba(0,0,0,0.45)";
332
494
  function isDarkSquare(square) {
333
495
  const fileIndex = square.charCodeAt(0) - 97;
334
496
  const rankNumber = Number(square[1]);
@@ -344,15 +506,118 @@ async function getPieceRaster(themeName, pieceKey, asset, squareSize) {
344
506
  pieceRasterCache.set(cacheKey, raster);
345
507
  return raster;
346
508
  }
509
+ function resolveInsideLabelColor(request, square) {
510
+ if (request.coordinates.color) {
511
+ return request.coordinates.color;
512
+ }
513
+ return isDarkSquare(square) ? INSIDE_LIGHT_LABEL_COLOR : INSIDE_DARK_LABEL_COLOR;
514
+ }
515
+ function resolveBorderCoordinateFontSize(context, geometry) {
516
+ const maxFontSize = Math.floor(
517
+ Math.min(geometry.squareSize * 0.6, geometry.borderSize * 0.65)
518
+ );
519
+ let fontSize = null;
520
+ for (let candidate = maxFontSize; candidate >= MIN_COORDINATE_FONT_SIZE; candidate -= 1) {
521
+ context.font = `${candidate}px sans-serif`;
522
+ const filesFit = geometry.borderFileLabels.every((label) => {
523
+ const metrics = context.measureText(label.text);
524
+ const textHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
525
+ return metrics.width <= geometry.squareSize * MAX_FILE_LABEL_WIDTH_RATIO && textHeight <= geometry.borderSize * MAX_LABEL_HEIGHT_RATIO;
526
+ });
527
+ const ranksFit = geometry.borderRankLabels.every((label) => {
528
+ const metrics = context.measureText(label.text);
529
+ const textHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
530
+ return metrics.width <= geometry.borderSize * MAX_RANK_LABEL_WIDTH_RATIO && textHeight <= geometry.squareSize * MAX_LABEL_HEIGHT_RATIO;
531
+ });
532
+ if (filesFit && ranksFit) {
533
+ fontSize = candidate;
534
+ break;
535
+ }
536
+ }
537
+ return fontSize;
538
+ }
539
+ function resolveInsideCoordinateFontSize(context, geometry) {
540
+ const maxFontSize = Math.floor(
541
+ geometry.squareSize * INSIDE_COORDINATE_MAX_FONT_RATIO
542
+ );
543
+ let fontSize = null;
544
+ for (let candidate = maxFontSize; candidate >= MIN_COORDINATE_FONT_SIZE; candidate -= 1) {
545
+ context.font = `${candidate}px sans-serif`;
546
+ const filesFit = geometry.insideFileLabels.every((label) => {
547
+ const metrics = context.measureText(label.text);
548
+ const textHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
549
+ return metrics.width <= geometry.insideLabelMaxWidth && textHeight <= geometry.insideLabelMaxHeight;
550
+ });
551
+ const ranksFit = geometry.insideRankLabels.every((label) => {
552
+ const metrics = context.measureText(label.text);
553
+ const textHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
554
+ return metrics.width <= geometry.insideLabelMaxWidth && textHeight <= geometry.insideLabelMaxHeight;
555
+ });
556
+ if (filesFit && ranksFit) {
557
+ fontSize = candidate;
558
+ break;
559
+ }
560
+ }
561
+ return fontSize;
562
+ }
563
+ function drawBorderCoordinates(context, request, geometry) {
564
+ if (geometry.borderSize === 0) {
565
+ return;
566
+ }
567
+ const fontSize = resolveBorderCoordinateFontSize(context, geometry);
568
+ if (fontSize === null) {
569
+ return;
570
+ }
571
+ context.fillStyle = request.coordinates.color ?? "#333";
572
+ context.font = `${fontSize}px sans-serif`;
573
+ context.textAlign = "center";
574
+ context.textBaseline = "middle";
575
+ for (const label of geometry.borderFileLabels) {
576
+ context.fillText(label.text, label.x, label.y);
577
+ }
578
+ for (const label of geometry.borderRankLabels) {
579
+ context.fillText(label.text, label.x, label.y);
580
+ }
581
+ }
582
+ function drawInsideCoordinates(context, request, geometry) {
583
+ const fontSize = resolveInsideCoordinateFontSize(context, geometry);
584
+ if (fontSize === null) {
585
+ return;
586
+ }
587
+ context.font = `${fontSize}px sans-serif`;
588
+ for (const label of geometry.insideFileLabels) {
589
+ context.fillStyle = resolveInsideLabelColor(request, label.square);
590
+ context.textAlign = label.textAlign;
591
+ context.textBaseline = label.textBaseline;
592
+ context.fillText(label.text, label.x, label.y);
593
+ }
594
+ for (const label of geometry.insideRankLabels) {
595
+ context.fillStyle = resolveInsideLabelColor(request, label.square);
596
+ context.textAlign = label.textAlign;
597
+ context.textBaseline = label.textBaseline;
598
+ context.fillText(label.text, label.x, label.y);
599
+ }
600
+ }
601
+ function drawCoordinates(context, request, geometry) {
602
+ if (!request.coordinates.enabled) {
603
+ return;
604
+ }
605
+ if (request.coordinates.position === "border") {
606
+ drawBorderCoordinates(context, request, geometry);
607
+ return;
608
+ }
609
+ drawInsideCoordinates(context, request, geometry);
610
+ }
347
611
  var CanvasPngRenderer = class {
348
612
  async render(request) {
349
613
  try {
350
614
  const geometry = createBoardGeometry({
351
615
  size: request.size,
352
616
  padding: request.padding,
617
+ borderSize: request.borderSize,
353
618
  flipped: request.flipped
354
619
  });
355
- const canvas = (0, import_canvas2.createCanvas)(geometry.imageWidth, geometry.imageHeight);
620
+ const canvas = (0, import_canvas3.createCanvas)(geometry.imageWidth, geometry.imageHeight);
356
621
  const context = canvas.getContext("2d");
357
622
  context.fillStyle = request.colors.lightSquare;
358
623
  context.fillRect(0, 0, geometry.imageWidth, geometry.imageHeight);
@@ -392,6 +657,7 @@ var CanvasPngRenderer = class {
392
657
  squareGeometry.size
393
658
  );
394
659
  }
660
+ drawCoordinates(context, request, geometry);
395
661
  return canvas.toBuffer("image/png");
396
662
  } catch (error) {
397
663
  if (error instanceof RenderError) {
@@ -554,11 +820,16 @@ function resolveTheme({ theme, style }) {
554
820
  // src/utils/normalization.ts
555
821
  var DEFAULT_SIZE = 480;
556
822
  var DEFAULT_PADDING = [0, 0, 0, 0];
823
+ var DEFAULT_BORDER_SIZE = 0;
557
824
  var DEFAULT_COLORS = {
558
825
  lightSquare: "#f0d9b5",
559
826
  darkSquare: "#b58863",
560
827
  highlight: "rgba(255, 206, 0, 0.45)"
561
828
  };
829
+ var DEFAULT_COORDINATES = {
830
+ enabled: false,
831
+ position: "inside"
832
+ };
562
833
  function normalizeColors(colors) {
563
834
  return {
564
835
  lightSquare: colors?.lightSquare ?? DEFAULT_COLORS.lightSquare,
@@ -566,17 +837,52 @@ function normalizeColors(colors) {
566
837
  highlight: colors?.highlight ?? DEFAULT_COLORS.highlight
567
838
  };
568
839
  }
840
+ function normalizeCoordinates(coordinates, borderSize) {
841
+ if (coordinates === void 0 || coordinates === false) {
842
+ return { ...DEFAULT_COORDINATES };
843
+ }
844
+ const defaultPosition = borderSize > 0 ? "border" : "inside";
845
+ if (coordinates === true) {
846
+ return {
847
+ enabled: true,
848
+ position: defaultPosition,
849
+ color: defaultPosition === "border" ? "#333" : void 0
850
+ };
851
+ }
852
+ if (coordinates === "border" || coordinates === "inside") {
853
+ return {
854
+ enabled: true,
855
+ position: coordinates,
856
+ color: coordinates === "border" ? "#333" : void 0
857
+ };
858
+ }
859
+ const position = coordinates.position ?? defaultPosition;
860
+ return {
861
+ enabled: coordinates.enabled ?? true,
862
+ position,
863
+ color: coordinates.color ?? (position === "border" ? "#333" : void 0)
864
+ };
865
+ }
569
866
  function normalizeRenderInputs(options) {
867
+ const size = validateSize(options.size ?? DEFAULT_SIZE);
868
+ const borderSize = validateBorderSize(
869
+ options.borderSize ?? DEFAULT_BORDER_SIZE,
870
+ size
871
+ );
872
+ validateBoardColors(options.colors);
873
+ validateCoordinatesOption(options.coordinates, borderSize);
570
874
  return {
571
- size: validateSize(options.size ?? DEFAULT_SIZE),
875
+ size,
572
876
  padding: normalizePadding(options.padding ?? DEFAULT_PADDING),
877
+ borderSize,
573
878
  flipped: options.flipped ?? false,
574
879
  theme: resolveTheme({
575
880
  theme: options.theme,
576
881
  style: options.style
577
882
  }),
578
883
  highlightSquares: normalizeHighlights(options.highlightSquares ?? []),
579
- colors: normalizeColors(options.colors)
884
+ colors: normalizeColors(options.colors),
885
+ coordinates: normalizeCoordinates(options.coordinates, borderSize)
580
886
  };
581
887
  }
582
888
 
@@ -625,8 +931,10 @@ var ChessImageGenerator = class {
625
931
  highlights: normalized.highlightSquares,
626
932
  size: normalized.size,
627
933
  padding: normalized.padding,
934
+ borderSize: normalized.borderSize,
628
935
  flipped: normalized.flipped,
629
- colors: normalized.colors
936
+ colors: normalized.colors,
937
+ coordinates: normalized.coordinates
630
938
  });
631
939
  }
632
940
  async toFile(filePath) {
@@ -666,8 +974,10 @@ async function renderChess(options) {
666
974
  highlights: normalized.highlightSquares,
667
975
  size: normalized.size,
668
976
  padding: normalized.padding,
977
+ borderSize: normalized.borderSize,
669
978
  flipped: normalized.flipped,
670
- colors: normalized.colors
979
+ colors: normalized.colors,
980
+ coordinates: normalized.coordinates
671
981
  });
672
982
  }
673
983
  // Annotate the CommonJS export names for ESM import in node: