chess2img 0.3.1 → 0.4.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.cjs CHANGED
@@ -27,7 +27,12 @@ __export(index_exports, {
27
27
  ThemeError: () => ThemeError,
28
28
  ValidationError: () => ValidationError,
29
29
  registerTheme: () => registerTheme,
30
- renderChess: () => renderChess
30
+ renderChess: () => renderChess,
31
+ renderFile: () => renderFile,
32
+ renderJpeg: () => renderJpeg,
33
+ renderJpegFile: () => renderJpegFile,
34
+ renderSvg: () => renderSvg,
35
+ renderSvgFile: () => renderSvgFile
31
36
  });
32
37
  module.exports = __toCommonJS(index_exports);
33
38
 
@@ -63,23 +68,6 @@ var IOError = class extends Error {
63
68
  }
64
69
  };
65
70
 
66
- // src/core/parsers.ts
67
- var import_chess = require("chess.js");
68
-
69
- // src/core/board.ts
70
- var FILES = ["a", "b", "c", "d", "e", "f", "g", "h"];
71
- var RANKS = ["8", "7", "6", "5", "4", "3", "2", "1"];
72
- var SQUARES = RANKS.flatMap(
73
- (rank) => FILES.map((file) => `${file}${rank}`)
74
- );
75
- function createEmptyBoardPosition() {
76
- return {
77
- squares: Object.fromEntries(
78
- SQUARES.map((square) => [square, null])
79
- )
80
- };
81
- }
82
-
83
71
  // src/core/validators.ts
84
72
  var import_canvas = require("canvas");
85
73
  var SQUARE_PATTERN = /^[a-h][1-8]$/;
@@ -272,71 +260,23 @@ function validateHighlightsInput(highlights, highlightSquares) {
272
260
  validateHighlightOptions(highlightSquares);
273
261
  }
274
262
 
275
- // src/core/parsers.ts
276
- var PIECE_SYMBOL_TO_KEY = {
277
- K: "wK",
278
- Q: "wQ",
279
- R: "wR",
280
- B: "wB",
281
- N: "wN",
282
- P: "wP",
283
- k: "bK",
284
- q: "bQ",
285
- r: "bR",
286
- b: "bB",
287
- n: "bN",
288
- p: "bP"
289
- };
290
- function chessBoardToBoardArray(board) {
291
- return board.map(
292
- (rank) => rank.map((piece) => {
293
- if (!piece) {
294
- return null;
295
- }
296
- return piece.color === "w" ? piece.type.toUpperCase() : piece.type;
297
- })
298
- );
299
- }
300
- function parseFEN(fen) {
301
- const chess = new import_chess.Chess();
302
- try {
303
- chess.load(fen);
304
- } catch (error) {
305
- throw new ParseError("Invalid FEN", { cause: error });
306
- }
307
- return parseBoardArray(chessBoardToBoardArray(chess.board()));
308
- }
309
- function parsePGN(pgn) {
310
- const chess = new import_chess.Chess();
311
- try {
312
- chess.loadPgn(pgn);
313
- } catch (error) {
314
- throw new ParseError("Invalid PGN", { cause: error });
315
- }
316
- return parseBoardArray(chessBoardToBoardArray(chess.board()));
317
- }
318
- function parseBoardArray(board) {
319
- const validatedBoard = validateBoardArray(board);
320
- const position = createEmptyBoardPosition();
321
- validatedBoard.forEach((rank, rankIndex) => {
322
- rank.forEach((cell, fileIndex) => {
323
- if (cell === null) {
324
- return;
325
- }
326
- const pieceKey = PIECE_SYMBOL_TO_KEY[cell];
327
- if (!pieceKey) {
328
- throw new ValidationError(`Invalid board piece: ${cell}`);
329
- }
330
- const square = `${FILES[fileIndex]}${8 - rankIndex}`;
331
- position.squares[square] = pieceKey;
332
- });
333
- });
334
- return position;
335
- }
336
-
337
263
  // src/render/canvas-renderer.ts
338
264
  var import_canvas3 = require("canvas");
339
265
 
266
+ // src/core/board.ts
267
+ var FILES = ["a", "b", "c", "d", "e", "f", "g", "h"];
268
+ var RANKS = ["8", "7", "6", "5", "4", "3", "2", "1"];
269
+ var SQUARES = RANKS.flatMap(
270
+ (rank) => FILES.map((file) => `${file}${rank}`)
271
+ );
272
+ function createEmptyBoardPosition() {
273
+ return {
274
+ squares: Object.fromEntries(
275
+ SQUARES.map((square) => [square, null])
276
+ )
277
+ };
278
+ }
279
+
340
280
  // src/core/geometry.ts
341
281
  function createBoardGeometry({
342
282
  size,
@@ -765,6 +705,9 @@ async function drawPieces(context, request, geometry) {
765
705
  }
766
706
  }
767
707
  var CanvasPngRenderer = class {
708
+ createOutputBuffer(canvas) {
709
+ return canvas.toBuffer("image/png");
710
+ }
768
711
  async render(request) {
769
712
  try {
770
713
  const geometry = createBoardGeometry({
@@ -782,7 +725,7 @@ var CanvasPngRenderer = class {
782
725
  drawCircleHighlights(context, request, geometry);
783
726
  drawCoordinates(context, request, geometry);
784
727
  await drawPieces(context, request, geometry);
785
- return canvas.toBuffer("image/png");
728
+ return this.createOutputBuffer(canvas);
786
729
  } catch (error) {
787
730
  if (error instanceof RenderError) {
788
731
  throw error;
@@ -792,11 +735,282 @@ var CanvasPngRenderer = class {
792
735
  }
793
736
  };
794
737
 
795
- // src/utils/io.ts
738
+ // src/render/canvas-jpeg-renderer.ts
739
+ var import_canvas4 = require("canvas");
740
+ var CanvasJpegRenderer = class extends CanvasPngRenderer {
741
+ createOutputBuffer(canvas) {
742
+ return canvas.toBuffer("image/jpeg");
743
+ }
744
+ };
745
+
746
+ // src/render/svg-renderer.ts
796
747
  var import_promises2 = require("fs/promises");
748
+ var import_canvas5 = require("canvas");
749
+ var svgSourceCache2 = new SourceAssetCache();
750
+ var binarySourceCache = new SourceAssetCache();
751
+ var measureContext = (0, import_canvas5.createCanvas)(1, 1).getContext("2d");
752
+ var MIN_COORDINATE_FONT_SIZE2 = 8;
753
+ var MAX_FILE_LABEL_WIDTH_RATIO2 = 0.75;
754
+ var MAX_RANK_LABEL_WIDTH_RATIO2 = 0.7;
755
+ var MAX_LABEL_HEIGHT_RATIO2 = 0.7;
756
+ var INSIDE_COORDINATE_MAX_FONT_RATIO2 = 0.34;
757
+ var INSIDE_LIGHT_LABEL_COLOR2 = "rgba(255,255,255,0.6)";
758
+ var INSIDE_DARK_LABEL_COLOR2 = "rgba(0,0,0,0.45)";
759
+ function escapeXml(value) {
760
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
761
+ }
762
+ function isDarkSquare2(square) {
763
+ const fileIndex = square.charCodeAt(0) - 97;
764
+ const rankNumber = Number(square[1]);
765
+ return (fileIndex + rankNumber) % 2 === 1;
766
+ }
767
+ function resolveInsideLabelColor2(request, square) {
768
+ if (request.coordinates.color) {
769
+ return request.coordinates.color;
770
+ }
771
+ return isDarkSquare2(square) ? INSIDE_LIGHT_LABEL_COLOR2 : INSIDE_DARK_LABEL_COLOR2;
772
+ }
773
+ function resolveHighlightOpacity2(style, color, opacity) {
774
+ if (opacity !== void 0) {
775
+ return opacity;
776
+ }
777
+ if (style === "circle" || color !== void 0) {
778
+ return 0.9;
779
+ }
780
+ return 1;
781
+ }
782
+ function resolveCircleLineWidth2(squareSize, lineWidth) {
783
+ const candidate = lineWidth ?? squareSize * 0.08;
784
+ return Math.max(2, Math.min(8, candidate));
785
+ }
786
+ function resolveCircleRadius2(squareSize, radius, lineWidth) {
787
+ const radiusPx = squareSize * (radius ?? 0.42);
788
+ return Math.max(0, radiusPx - lineWidth / 2);
789
+ }
790
+ function resolveBorderCoordinateFontSize2(geometry) {
791
+ const maxFontSize = Math.floor(
792
+ Math.min(geometry.squareSize * 0.6, geometry.borderSize * 0.65)
793
+ );
794
+ for (let candidate = maxFontSize; candidate >= MIN_COORDINATE_FONT_SIZE2; candidate -= 1) {
795
+ measureContext.font = `${candidate}px sans-serif`;
796
+ const filesFit = geometry.borderFileLabels.every((label) => {
797
+ const metrics = measureContext.measureText(label.text);
798
+ const textHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
799
+ return metrics.width <= geometry.squareSize * MAX_FILE_LABEL_WIDTH_RATIO2 && textHeight <= geometry.borderSize * MAX_LABEL_HEIGHT_RATIO2;
800
+ });
801
+ const ranksFit = geometry.borderRankLabels.every((label) => {
802
+ const metrics = measureContext.measureText(label.text);
803
+ const textHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
804
+ return metrics.width <= geometry.borderSize * MAX_RANK_LABEL_WIDTH_RATIO2 && textHeight <= geometry.squareSize * MAX_LABEL_HEIGHT_RATIO2;
805
+ });
806
+ if (filesFit && ranksFit) {
807
+ return candidate;
808
+ }
809
+ }
810
+ return null;
811
+ }
812
+ function resolveInsideCoordinateFontSize2(geometry) {
813
+ const maxFontSize = Math.floor(
814
+ geometry.squareSize * INSIDE_COORDINATE_MAX_FONT_RATIO2
815
+ );
816
+ for (let candidate = maxFontSize; candidate >= MIN_COORDINATE_FONT_SIZE2; candidate -= 1) {
817
+ measureContext.font = `${candidate}px sans-serif`;
818
+ const filesFit = geometry.insideFileLabels.every((label) => {
819
+ const metrics = measureContext.measureText(label.text);
820
+ const textHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
821
+ return metrics.width <= geometry.insideLabelMaxWidth && textHeight <= geometry.insideLabelMaxHeight;
822
+ });
823
+ const ranksFit = geometry.insideRankLabels.every((label) => {
824
+ const metrics = measureContext.measureText(label.text);
825
+ const textHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
826
+ return metrics.width <= geometry.insideLabelMaxWidth && textHeight <= geometry.insideLabelMaxHeight;
827
+ });
828
+ if (filesFit && ranksFit) {
829
+ return candidate;
830
+ }
831
+ }
832
+ return null;
833
+ }
834
+ function textAnchor(value) {
835
+ if (value === "center") {
836
+ return "middle";
837
+ }
838
+ return value === "right" ? "end" : "start";
839
+ }
840
+ function dominantBaseline(value) {
841
+ if (value === "middle") {
842
+ return "middle";
843
+ }
844
+ return value === "bottom" ? "text-after-edge" : "hanging";
845
+ }
846
+ async function readSvgSource2(filePath) {
847
+ const cached = svgSourceCache2.get(filePath);
848
+ if (cached) {
849
+ return cached;
850
+ }
851
+ try {
852
+ const source = await (0, import_promises2.readFile)(filePath, "utf8");
853
+ svgSourceCache2.set(filePath, source);
854
+ return source;
855
+ } catch (error) {
856
+ throw new RenderError(`Failed to read SVG asset: ${filePath}`, { cause: error });
857
+ }
858
+ }
859
+ async function readBinarySource(filePath) {
860
+ const cached = binarySourceCache.get(filePath);
861
+ if (cached) {
862
+ return cached;
863
+ }
864
+ try {
865
+ const source = await (0, import_promises2.readFile)(filePath);
866
+ binarySourceCache.set(filePath, source);
867
+ return source;
868
+ } catch (error) {
869
+ throw new RenderError(`Failed to read image asset: ${filePath}`, { cause: error });
870
+ }
871
+ }
872
+ function stripSvgPreamble(source) {
873
+ return source.replace(/^\uFEFF/, "").replace(/<\?xml[\s\S]*?\?>/gi, "").replace(/<!doctype[\s\S]*?>/gi, "").trim();
874
+ }
875
+ function inlineSvgPiece(source, x, y, size) {
876
+ const sanitized = stripSvgPreamble(source);
877
+ if (!sanitized.startsWith("<svg")) {
878
+ throw new RenderError("Invalid SVG asset source");
879
+ }
880
+ return sanitized.replace(/<svg\b([^>]*)>/i, (_match, attrs) => {
881
+ const cleanedAttrs = attrs.replace(/\s(?:x|y|width|height)=(".*?"|'.*?'|[^\s>]+)/gi, "").trim();
882
+ const preservedAttrs = cleanedAttrs ? ` ${cleanedAttrs}` : "";
883
+ return `<svg x="${x}" y="${y}" width="${size}" height="${size}"${preservedAttrs}>`;
884
+ });
885
+ }
886
+ async function renderPieceElement(asset, x, y, size) {
887
+ if (asset.kind === "svg") {
888
+ const source = await readSvgSource2(asset.source);
889
+ return inlineSvgPiece(source, x, y, size);
890
+ }
891
+ const buffer = await readBinarySource(asset.source);
892
+ return [
893
+ `<image x="${x}" y="${y}" width="${size}" height="${size}"`,
894
+ ` href="data:image/png;base64,${buffer.toString("base64")}" />`
895
+ ].join("");
896
+ }
897
+ async function renderPieces(request, geometry) {
898
+ const pieces = [];
899
+ for (const square of SQUARES) {
900
+ const pieceKey = request.board.squares[square];
901
+ if (!pieceKey) {
902
+ continue;
903
+ }
904
+ const squareGeometry = geometry.squares[square];
905
+ pieces.push(
906
+ await renderPieceElement(
907
+ request.theme.pieces[pieceKey],
908
+ squareGeometry.x,
909
+ squareGeometry.y,
910
+ squareGeometry.size
911
+ )
912
+ );
913
+ }
914
+ return pieces;
915
+ }
916
+ function renderCoordinates(request, geometry) {
917
+ if (!request.coordinates.enabled) {
918
+ return [];
919
+ }
920
+ if (request.coordinates.position === "border") {
921
+ if (geometry.borderSize === 0) {
922
+ return [];
923
+ }
924
+ const fontSize2 = resolveBorderCoordinateFontSize2(geometry);
925
+ if (fontSize2 === null) {
926
+ return [];
927
+ }
928
+ return [
929
+ ...geometry.borderFileLabels,
930
+ ...geometry.borderRankLabels
931
+ ].map(
932
+ (label) => `<text x="${label.x}" y="${label.y}" fill="${escapeXml(request.coordinates.color ?? "#333")}" font-family="sans-serif" font-size="${fontSize2}" text-anchor="middle" dominant-baseline="middle">${escapeXml(label.text)}</text>`
933
+ );
934
+ }
935
+ const fontSize = resolveInsideCoordinateFontSize2(geometry);
936
+ if (fontSize === null) {
937
+ return [];
938
+ }
939
+ return [
940
+ ...geometry.insideFileLabels.map(
941
+ (label) => `<text x="${label.x}" y="${label.y}" fill="${escapeXml(resolveInsideLabelColor2(request, label.square))}" font-family="sans-serif" font-size="${fontSize}" text-anchor="${textAnchor(label.textAlign)}" dominant-baseline="${dominantBaseline(label.textBaseline)}">${escapeXml(label.text)}</text>`
942
+ ),
943
+ ...geometry.insideRankLabels.map(
944
+ (label) => `<text x="${label.x}" y="${label.y}" fill="${escapeXml(resolveInsideLabelColor2(request, label.square))}" font-family="sans-serif" font-size="${fontSize}" text-anchor="${textAnchor(label.textAlign)}" dominant-baseline="${dominantBaseline(label.textBaseline)}">${escapeXml(label.text)}</text>`
945
+ )
946
+ ];
947
+ }
948
+ var SvgRenderer = class {
949
+ async render(request) {
950
+ try {
951
+ const geometry = createBoardGeometry({
952
+ size: request.size,
953
+ padding: request.padding,
954
+ borderSize: request.borderSize,
955
+ flipped: request.flipped
956
+ });
957
+ const elements = [
958
+ `<rect x="0" y="0" width="${geometry.imageWidth}" height="${geometry.imageHeight}" fill="${escapeXml(request.colors.lightSquare)}" />`
959
+ ];
960
+ for (const square of SQUARES) {
961
+ const squareGeometry = geometry.squares[square];
962
+ elements.push(
963
+ `<rect x="${squareGeometry.x}" y="${squareGeometry.y}" width="${squareGeometry.size}" height="${squareGeometry.size}" fill="${escapeXml(isDarkSquare2(square) ? request.colors.darkSquare : request.colors.lightSquare)}" />`
964
+ );
965
+ }
966
+ for (const highlight of request.highlights) {
967
+ if (highlight.style !== "fill") {
968
+ continue;
969
+ }
970
+ const squareGeometry = geometry.squares[highlight.square];
971
+ elements.push(
972
+ `<rect x="${squareGeometry.x}" y="${squareGeometry.y}" width="${squareGeometry.size}" height="${squareGeometry.size}" fill="${escapeXml(highlight.color ?? request.colors.highlight)}" fill-opacity="${resolveHighlightOpacity2(highlight.style, highlight.color, highlight.opacity)}" />`
973
+ );
974
+ }
975
+ for (const highlight of request.highlights) {
976
+ if (highlight.style !== "circle") {
977
+ continue;
978
+ }
979
+ const squareGeometry = geometry.squares[highlight.square];
980
+ const lineWidth = resolveCircleLineWidth2(squareGeometry.size, highlight.lineWidth);
981
+ const radius = resolveCircleRadius2(squareGeometry.size, highlight.radius, lineWidth);
982
+ elements.push(
983
+ `<circle cx="${squareGeometry.x + squareGeometry.size / 2}" cy="${squareGeometry.y + squareGeometry.size / 2}" r="${radius}" fill="none" stroke="${escapeXml(highlight.color ?? "#ffcc00")}" stroke-width="${lineWidth}" stroke-opacity="${resolveHighlightOpacity2(highlight.style, highlight.color, highlight.opacity)}" />`
984
+ );
985
+ }
986
+ elements.push(...renderCoordinates(request, geometry));
987
+ elements.push(...await renderPieces(request, geometry));
988
+ return [
989
+ `<svg xmlns="http://www.w3.org/2000/svg" width="${geometry.imageWidth}" height="${geometry.imageHeight}" viewBox="0 0 ${geometry.imageWidth} ${geometry.imageHeight}">`,
990
+ ...elements,
991
+ "</svg>"
992
+ ].join("");
993
+ } catch (error) {
994
+ if (error instanceof RenderError) {
995
+ throw error;
996
+ }
997
+ throw new RenderError("Failed to render chess board as SVG", { cause: error });
998
+ }
999
+ }
1000
+ };
1001
+
1002
+ // src/utils/io.ts
1003
+ var import_promises3 = require("fs/promises");
797
1004
  async function writeBufferToFile(filePath, buffer) {
798
1005
  try {
799
- await (0, import_promises2.writeFile)(filePath, buffer);
1006
+ await (0, import_promises3.writeFile)(filePath, buffer);
1007
+ } catch (error) {
1008
+ throw new IOError(`Failed to write file: ${filePath}`, { cause: error });
1009
+ }
1010
+ }
1011
+ async function writeStringToFile(filePath, contents) {
1012
+ try {
1013
+ await (0, import_promises3.writeFile)(filePath, contents, "utf8");
800
1014
  } catch (error) {
801
1015
  throw new IOError(`Failed to write file: ${filePath}`, { cause: error });
802
1016
  }
@@ -1040,6 +1254,112 @@ function normalizeRenderInputs(options) {
1040
1254
  };
1041
1255
  }
1042
1256
 
1257
+ // src/core/parsers.ts
1258
+ var import_chess = require("chess.js");
1259
+ var PIECE_SYMBOL_TO_KEY = {
1260
+ K: "wK",
1261
+ Q: "wQ",
1262
+ R: "wR",
1263
+ B: "wB",
1264
+ N: "wN",
1265
+ P: "wP",
1266
+ k: "bK",
1267
+ q: "bQ",
1268
+ r: "bR",
1269
+ b: "bB",
1270
+ n: "bN",
1271
+ p: "bP"
1272
+ };
1273
+ function chessBoardToBoardArray(board) {
1274
+ return board.map(
1275
+ (rank) => rank.map((piece) => {
1276
+ if (!piece) {
1277
+ return null;
1278
+ }
1279
+ return piece.color === "w" ? piece.type.toUpperCase() : piece.type;
1280
+ })
1281
+ );
1282
+ }
1283
+ function parseFEN(fen) {
1284
+ const chess = new import_chess.Chess();
1285
+ try {
1286
+ chess.load(fen);
1287
+ } catch (error) {
1288
+ throw new ParseError("Invalid FEN", { cause: error });
1289
+ }
1290
+ return parseBoardArray(chessBoardToBoardArray(chess.board()));
1291
+ }
1292
+ function parsePGN(pgn) {
1293
+ const chess = new import_chess.Chess();
1294
+ try {
1295
+ chess.loadPgn(pgn);
1296
+ } catch (error) {
1297
+ throw new ParseError("Invalid PGN", { cause: error });
1298
+ }
1299
+ return parseBoardArray(chessBoardToBoardArray(chess.board()));
1300
+ }
1301
+ function parseBoardArray(board) {
1302
+ const validatedBoard = validateBoardArray(board);
1303
+ const position = createEmptyBoardPosition();
1304
+ validatedBoard.forEach((rank, rankIndex) => {
1305
+ rank.forEach((cell, fileIndex) => {
1306
+ if (cell === null) {
1307
+ return;
1308
+ }
1309
+ const pieceKey = PIECE_SYMBOL_TO_KEY[cell];
1310
+ if (!pieceKey) {
1311
+ throw new ValidationError(`Invalid board piece: ${cell}`);
1312
+ }
1313
+ const square = `${FILES[fileIndex]}${8 - rankIndex}`;
1314
+ position.squares[square] = pieceKey;
1315
+ });
1316
+ });
1317
+ return position;
1318
+ }
1319
+
1320
+ // src/api/render-request.ts
1321
+ function parseInputPosition(options) {
1322
+ if (typeof options.fen === "string") {
1323
+ return parseFEN(options.fen);
1324
+ }
1325
+ if (typeof options.pgn === "string") {
1326
+ return parsePGN(options.pgn);
1327
+ }
1328
+ if (Array.isArray(options.board)) {
1329
+ return parseBoardArray(options.board);
1330
+ }
1331
+ throw new ValidationError("Exactly one of fen, pgn, or board must be provided");
1332
+ }
1333
+ function validateSingleInputSource(options) {
1334
+ const provided = [
1335
+ typeof options.fen === "string" ? options.fen : void 0,
1336
+ typeof options.pgn === "string" ? options.pgn : void 0,
1337
+ Array.isArray(options.board) ? options.board : void 0
1338
+ ].filter((value) => value !== void 0);
1339
+ if (provided.length !== 1) {
1340
+ throw new ValidationError("Exactly one of fen, pgn, or board must be provided");
1341
+ }
1342
+ }
1343
+ function createRenderRequest(board, options) {
1344
+ const normalized = normalizeRenderInputs(options);
1345
+ return {
1346
+ board,
1347
+ theme: normalized.theme,
1348
+ highlights: normalized.highlights,
1349
+ size: normalized.size,
1350
+ padding: normalized.padding,
1351
+ borderSize: normalized.borderSize,
1352
+ flipped: normalized.flipped,
1353
+ colors: normalized.colors,
1354
+ coordinates: normalized.coordinates
1355
+ };
1356
+ }
1357
+ function createRenderRequestFromOptions(options) {
1358
+ validateSingleInputSource(options);
1359
+ const board = parseInputPosition(options);
1360
+ return createRenderRequest(board, options);
1361
+ }
1362
+
1043
1363
  // src/api/class-api.ts
1044
1364
  var ChessImageGenerator = class {
1045
1365
  position = null;
@@ -1073,65 +1393,70 @@ var ChessImageGenerator = class {
1073
1393
  throw new ValidationError("No board position loaded");
1074
1394
  }
1075
1395
  const renderer = new CanvasPngRenderer();
1076
- const normalized = normalizeRenderInputs({
1396
+ const request = createRenderRequest(this.position, {
1077
1397
  ...this.defaults,
1078
1398
  highlights: this.highlights,
1079
1399
  highlightSquares: void 0
1080
1400
  });
1081
- return renderer.render({
1082
- board: this.position,
1083
- theme: normalized.theme,
1084
- highlights: normalized.highlights,
1085
- size: normalized.size,
1086
- padding: normalized.padding,
1087
- borderSize: normalized.borderSize,
1088
- flipped: normalized.flipped,
1089
- colors: normalized.colors,
1090
- coordinates: normalized.coordinates
1091
- });
1401
+ return renderer.render(request);
1092
1402
  }
1093
1403
  async toFile(filePath) {
1094
1404
  const buffer = await this.toBuffer();
1095
1405
  await writeBufferToFile(filePath, buffer);
1096
1406
  }
1097
- };
1098
-
1099
- // src/api/functional-api.ts
1100
- function parseInputPosition(options) {
1101
- if (typeof options.fen === "string") {
1102
- return parseFEN(options.fen);
1407
+ async toSvg() {
1408
+ if (!this.position) {
1409
+ throw new ValidationError("No board position loaded");
1410
+ }
1411
+ const renderer = new SvgRenderer();
1412
+ const request = createRenderRequest(this.position, {
1413
+ ...this.defaults,
1414
+ highlights: this.highlights,
1415
+ highlightSquares: void 0
1416
+ });
1417
+ return renderer.render(request);
1103
1418
  }
1104
- if (typeof options.pgn === "string") {
1105
- return parsePGN(options.pgn);
1419
+ async toSvgFile(filePath) {
1420
+ await writeStringToFile(filePath, await this.toSvg());
1106
1421
  }
1107
- if (Array.isArray(options.board)) {
1108
- return parseBoardArray(options.board);
1422
+ async toJpeg() {
1423
+ if (!this.position) {
1424
+ throw new ValidationError("No board position loaded");
1425
+ }
1426
+ const renderer = new CanvasJpegRenderer();
1427
+ const request = createRenderRequest(this.position, {
1428
+ ...this.defaults,
1429
+ highlights: this.highlights,
1430
+ highlightSquares: void 0
1431
+ });
1432
+ return renderer.render(request);
1109
1433
  }
1110
- throw new ValidationError("Exactly one of fen, pgn, or board must be provided");
1111
- }
1112
- async function renderChess(options) {
1113
- const provided = [
1114
- typeof options.fen === "string" ? options.fen : void 0,
1115
- typeof options.pgn === "string" ? options.pgn : void 0,
1116
- Array.isArray(options.board) ? options.board : void 0
1117
- ].filter((value) => value !== void 0);
1118
- if (provided.length !== 1) {
1119
- throw new ValidationError("Exactly one of fen, pgn, or board must be provided");
1434
+ async toJpegFile(filePath) {
1435
+ await writeBufferToFile(filePath, await this.toJpeg());
1120
1436
  }
1121
- const position = parseInputPosition(options);
1437
+ };
1438
+
1439
+ // src/api/functional-api.ts
1440
+ async function renderChess(options) {
1122
1441
  const renderer = new CanvasPngRenderer();
1123
- const normalized = normalizeRenderInputs(options);
1124
- return renderer.render({
1125
- board: position,
1126
- theme: normalized.theme,
1127
- highlights: normalized.highlights,
1128
- size: normalized.size,
1129
- padding: normalized.padding,
1130
- borderSize: normalized.borderSize,
1131
- flipped: normalized.flipped,
1132
- colors: normalized.colors,
1133
- coordinates: normalized.coordinates
1134
- });
1442
+ return renderer.render(createRenderRequestFromOptions(options));
1443
+ }
1444
+ async function renderSvg(options) {
1445
+ const renderer = new SvgRenderer();
1446
+ return renderer.render(createRenderRequestFromOptions(options));
1447
+ }
1448
+ async function renderJpeg(options) {
1449
+ const renderer = new CanvasJpegRenderer();
1450
+ return renderer.render(createRenderRequestFromOptions(options));
1451
+ }
1452
+ async function renderFile(filePath, options) {
1453
+ await writeBufferToFile(filePath, await renderChess(options));
1454
+ }
1455
+ async function renderSvgFile(filePath, options) {
1456
+ await writeStringToFile(filePath, await renderSvg(options));
1457
+ }
1458
+ async function renderJpegFile(filePath, options) {
1459
+ await writeBufferToFile(filePath, await renderJpeg(options));
1135
1460
  }
1136
1461
  // Annotate the CommonJS export names for ESM import in node:
1137
1462
  0 && (module.exports = {
@@ -1142,6 +1467,11 @@ async function renderChess(options) {
1142
1467
  ThemeError,
1143
1468
  ValidationError,
1144
1469
  registerTheme,
1145
- renderChess
1470
+ renderChess,
1471
+ renderFile,
1472
+ renderJpeg,
1473
+ renderJpegFile,
1474
+ renderSvg,
1475
+ renderSvgFile
1146
1476
  });
1147
1477
  //# sourceMappingURL=index.cjs.map