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.js CHANGED
@@ -37,23 +37,6 @@ var IOError = class extends Error {
37
37
  }
38
38
  };
39
39
 
40
- // src/core/parsers.ts
41
- import { Chess } from "chess.js";
42
-
43
- // src/core/board.ts
44
- var FILES = ["a", "b", "c", "d", "e", "f", "g", "h"];
45
- var RANKS = ["8", "7", "6", "5", "4", "3", "2", "1"];
46
- var SQUARES = RANKS.flatMap(
47
- (rank) => FILES.map((file) => `${file}${rank}`)
48
- );
49
- function createEmptyBoardPosition() {
50
- return {
51
- squares: Object.fromEntries(
52
- SQUARES.map((square) => [square, null])
53
- )
54
- };
55
- }
56
-
57
40
  // src/core/validators.ts
58
41
  import { createCanvas } from "canvas";
59
42
  var SQUARE_PATTERN = /^[a-h][1-8]$/;
@@ -246,71 +229,23 @@ function validateHighlightsInput(highlights, highlightSquares) {
246
229
  validateHighlightOptions(highlightSquares);
247
230
  }
248
231
 
249
- // src/core/parsers.ts
250
- var PIECE_SYMBOL_TO_KEY = {
251
- K: "wK",
252
- Q: "wQ",
253
- R: "wR",
254
- B: "wB",
255
- N: "wN",
256
- P: "wP",
257
- k: "bK",
258
- q: "bQ",
259
- r: "bR",
260
- b: "bB",
261
- n: "bN",
262
- p: "bP"
263
- };
264
- function chessBoardToBoardArray(board) {
265
- return board.map(
266
- (rank) => rank.map((piece) => {
267
- if (!piece) {
268
- return null;
269
- }
270
- return piece.color === "w" ? piece.type.toUpperCase() : piece.type;
271
- })
272
- );
273
- }
274
- function parseFEN(fen) {
275
- const chess = new Chess();
276
- try {
277
- chess.load(fen);
278
- } catch (error) {
279
- throw new ParseError("Invalid FEN", { cause: error });
280
- }
281
- return parseBoardArray(chessBoardToBoardArray(chess.board()));
282
- }
283
- function parsePGN(pgn) {
284
- const chess = new Chess();
285
- try {
286
- chess.loadPgn(pgn);
287
- } catch (error) {
288
- throw new ParseError("Invalid PGN", { cause: error });
289
- }
290
- return parseBoardArray(chessBoardToBoardArray(chess.board()));
291
- }
292
- function parseBoardArray(board) {
293
- const validatedBoard = validateBoardArray(board);
294
- const position = createEmptyBoardPosition();
295
- validatedBoard.forEach((rank, rankIndex) => {
296
- rank.forEach((cell, fileIndex) => {
297
- if (cell === null) {
298
- return;
299
- }
300
- const pieceKey = PIECE_SYMBOL_TO_KEY[cell];
301
- if (!pieceKey) {
302
- throw new ValidationError(`Invalid board piece: ${cell}`);
303
- }
304
- const square = `${FILES[fileIndex]}${8 - rankIndex}`;
305
- position.squares[square] = pieceKey;
306
- });
307
- });
308
- return position;
309
- }
310
-
311
232
  // src/render/canvas-renderer.ts
312
233
  import { createCanvas as createCanvas3 } from "canvas";
313
234
 
235
+ // src/core/board.ts
236
+ var FILES = ["a", "b", "c", "d", "e", "f", "g", "h"];
237
+ var RANKS = ["8", "7", "6", "5", "4", "3", "2", "1"];
238
+ var SQUARES = RANKS.flatMap(
239
+ (rank) => FILES.map((file) => `${file}${rank}`)
240
+ );
241
+ function createEmptyBoardPosition() {
242
+ return {
243
+ squares: Object.fromEntries(
244
+ SQUARES.map((square) => [square, null])
245
+ )
246
+ };
247
+ }
248
+
314
249
  // src/core/geometry.ts
315
250
  function createBoardGeometry({
316
251
  size,
@@ -739,6 +674,9 @@ async function drawPieces(context, request, geometry) {
739
674
  }
740
675
  }
741
676
  var CanvasPngRenderer = class {
677
+ createOutputBuffer(canvas) {
678
+ return canvas.toBuffer("image/png");
679
+ }
742
680
  async render(request) {
743
681
  try {
744
682
  const geometry = createBoardGeometry({
@@ -756,7 +694,7 @@ var CanvasPngRenderer = class {
756
694
  drawCircleHighlights(context, request, geometry);
757
695
  drawCoordinates(context, request, geometry);
758
696
  await drawPieces(context, request, geometry);
759
- return canvas.toBuffer("image/png");
697
+ return this.createOutputBuffer(canvas);
760
698
  } catch (error) {
761
699
  if (error instanceof RenderError) {
762
700
  throw error;
@@ -766,6 +704,270 @@ var CanvasPngRenderer = class {
766
704
  }
767
705
  };
768
706
 
707
+ // src/render/canvas-jpeg-renderer.ts
708
+ import "canvas";
709
+ var CanvasJpegRenderer = class extends CanvasPngRenderer {
710
+ createOutputBuffer(canvas) {
711
+ return canvas.toBuffer("image/jpeg");
712
+ }
713
+ };
714
+
715
+ // src/render/svg-renderer.ts
716
+ import { readFile as readFile2 } from "fs/promises";
717
+ import { createCanvas as createCanvas5 } from "canvas";
718
+ var svgSourceCache2 = new SourceAssetCache();
719
+ var binarySourceCache = new SourceAssetCache();
720
+ var measureContext = createCanvas5(1, 1).getContext("2d");
721
+ var MIN_COORDINATE_FONT_SIZE2 = 8;
722
+ var MAX_FILE_LABEL_WIDTH_RATIO2 = 0.75;
723
+ var MAX_RANK_LABEL_WIDTH_RATIO2 = 0.7;
724
+ var MAX_LABEL_HEIGHT_RATIO2 = 0.7;
725
+ var INSIDE_COORDINATE_MAX_FONT_RATIO2 = 0.34;
726
+ var INSIDE_LIGHT_LABEL_COLOR2 = "rgba(255,255,255,0.6)";
727
+ var INSIDE_DARK_LABEL_COLOR2 = "rgba(0,0,0,0.45)";
728
+ function escapeXml(value) {
729
+ return value.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
730
+ }
731
+ function isDarkSquare2(square) {
732
+ const fileIndex = square.charCodeAt(0) - 97;
733
+ const rankNumber = Number(square[1]);
734
+ return (fileIndex + rankNumber) % 2 === 1;
735
+ }
736
+ function resolveInsideLabelColor2(request, square) {
737
+ if (request.coordinates.color) {
738
+ return request.coordinates.color;
739
+ }
740
+ return isDarkSquare2(square) ? INSIDE_LIGHT_LABEL_COLOR2 : INSIDE_DARK_LABEL_COLOR2;
741
+ }
742
+ function resolveHighlightOpacity2(style, color, opacity) {
743
+ if (opacity !== void 0) {
744
+ return opacity;
745
+ }
746
+ if (style === "circle" || color !== void 0) {
747
+ return 0.9;
748
+ }
749
+ return 1;
750
+ }
751
+ function resolveCircleLineWidth2(squareSize, lineWidth) {
752
+ const candidate = lineWidth ?? squareSize * 0.08;
753
+ return Math.max(2, Math.min(8, candidate));
754
+ }
755
+ function resolveCircleRadius2(squareSize, radius, lineWidth) {
756
+ const radiusPx = squareSize * (radius ?? 0.42);
757
+ return Math.max(0, radiusPx - lineWidth / 2);
758
+ }
759
+ function resolveBorderCoordinateFontSize2(geometry) {
760
+ const maxFontSize = Math.floor(
761
+ Math.min(geometry.squareSize * 0.6, geometry.borderSize * 0.65)
762
+ );
763
+ for (let candidate = maxFontSize; candidate >= MIN_COORDINATE_FONT_SIZE2; candidate -= 1) {
764
+ measureContext.font = `${candidate}px sans-serif`;
765
+ const filesFit = geometry.borderFileLabels.every((label) => {
766
+ const metrics = measureContext.measureText(label.text);
767
+ const textHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
768
+ return metrics.width <= geometry.squareSize * MAX_FILE_LABEL_WIDTH_RATIO2 && textHeight <= geometry.borderSize * MAX_LABEL_HEIGHT_RATIO2;
769
+ });
770
+ const ranksFit = geometry.borderRankLabels.every((label) => {
771
+ const metrics = measureContext.measureText(label.text);
772
+ const textHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
773
+ return metrics.width <= geometry.borderSize * MAX_RANK_LABEL_WIDTH_RATIO2 && textHeight <= geometry.squareSize * MAX_LABEL_HEIGHT_RATIO2;
774
+ });
775
+ if (filesFit && ranksFit) {
776
+ return candidate;
777
+ }
778
+ }
779
+ return null;
780
+ }
781
+ function resolveInsideCoordinateFontSize2(geometry) {
782
+ const maxFontSize = Math.floor(
783
+ geometry.squareSize * INSIDE_COORDINATE_MAX_FONT_RATIO2
784
+ );
785
+ for (let candidate = maxFontSize; candidate >= MIN_COORDINATE_FONT_SIZE2; candidate -= 1) {
786
+ measureContext.font = `${candidate}px sans-serif`;
787
+ const filesFit = geometry.insideFileLabels.every((label) => {
788
+ const metrics = measureContext.measureText(label.text);
789
+ const textHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
790
+ return metrics.width <= geometry.insideLabelMaxWidth && textHeight <= geometry.insideLabelMaxHeight;
791
+ });
792
+ const ranksFit = geometry.insideRankLabels.every((label) => {
793
+ const metrics = measureContext.measureText(label.text);
794
+ const textHeight = metrics.actualBoundingBoxAscent + metrics.actualBoundingBoxDescent;
795
+ return metrics.width <= geometry.insideLabelMaxWidth && textHeight <= geometry.insideLabelMaxHeight;
796
+ });
797
+ if (filesFit && ranksFit) {
798
+ return candidate;
799
+ }
800
+ }
801
+ return null;
802
+ }
803
+ function textAnchor(value) {
804
+ if (value === "center") {
805
+ return "middle";
806
+ }
807
+ return value === "right" ? "end" : "start";
808
+ }
809
+ function dominantBaseline(value) {
810
+ if (value === "middle") {
811
+ return "middle";
812
+ }
813
+ return value === "bottom" ? "text-after-edge" : "hanging";
814
+ }
815
+ async function readSvgSource2(filePath) {
816
+ const cached = svgSourceCache2.get(filePath);
817
+ if (cached) {
818
+ return cached;
819
+ }
820
+ try {
821
+ const source = await readFile2(filePath, "utf8");
822
+ svgSourceCache2.set(filePath, source);
823
+ return source;
824
+ } catch (error) {
825
+ throw new RenderError(`Failed to read SVG asset: ${filePath}`, { cause: error });
826
+ }
827
+ }
828
+ async function readBinarySource(filePath) {
829
+ const cached = binarySourceCache.get(filePath);
830
+ if (cached) {
831
+ return cached;
832
+ }
833
+ try {
834
+ const source = await readFile2(filePath);
835
+ binarySourceCache.set(filePath, source);
836
+ return source;
837
+ } catch (error) {
838
+ throw new RenderError(`Failed to read image asset: ${filePath}`, { cause: error });
839
+ }
840
+ }
841
+ function stripSvgPreamble(source) {
842
+ return source.replace(/^\uFEFF/, "").replace(/<\?xml[\s\S]*?\?>/gi, "").replace(/<!doctype[\s\S]*?>/gi, "").trim();
843
+ }
844
+ function inlineSvgPiece(source, x, y, size) {
845
+ const sanitized = stripSvgPreamble(source);
846
+ if (!sanitized.startsWith("<svg")) {
847
+ throw new RenderError("Invalid SVG asset source");
848
+ }
849
+ return sanitized.replace(/<svg\b([^>]*)>/i, (_match, attrs) => {
850
+ const cleanedAttrs = attrs.replace(/\s(?:x|y|width|height)=(".*?"|'.*?'|[^\s>]+)/gi, "").trim();
851
+ const preservedAttrs = cleanedAttrs ? ` ${cleanedAttrs}` : "";
852
+ return `<svg x="${x}" y="${y}" width="${size}" height="${size}"${preservedAttrs}>`;
853
+ });
854
+ }
855
+ async function renderPieceElement(asset, x, y, size) {
856
+ if (asset.kind === "svg") {
857
+ const source = await readSvgSource2(asset.source);
858
+ return inlineSvgPiece(source, x, y, size);
859
+ }
860
+ const buffer = await readBinarySource(asset.source);
861
+ return [
862
+ `<image x="${x}" y="${y}" width="${size}" height="${size}"`,
863
+ ` href="data:image/png;base64,${buffer.toString("base64")}" />`
864
+ ].join("");
865
+ }
866
+ async function renderPieces(request, geometry) {
867
+ const pieces = [];
868
+ for (const square of SQUARES) {
869
+ const pieceKey = request.board.squares[square];
870
+ if (!pieceKey) {
871
+ continue;
872
+ }
873
+ const squareGeometry = geometry.squares[square];
874
+ pieces.push(
875
+ await renderPieceElement(
876
+ request.theme.pieces[pieceKey],
877
+ squareGeometry.x,
878
+ squareGeometry.y,
879
+ squareGeometry.size
880
+ )
881
+ );
882
+ }
883
+ return pieces;
884
+ }
885
+ function renderCoordinates(request, geometry) {
886
+ if (!request.coordinates.enabled) {
887
+ return [];
888
+ }
889
+ if (request.coordinates.position === "border") {
890
+ if (geometry.borderSize === 0) {
891
+ return [];
892
+ }
893
+ const fontSize2 = resolveBorderCoordinateFontSize2(geometry);
894
+ if (fontSize2 === null) {
895
+ return [];
896
+ }
897
+ return [
898
+ ...geometry.borderFileLabels,
899
+ ...geometry.borderRankLabels
900
+ ].map(
901
+ (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>`
902
+ );
903
+ }
904
+ const fontSize = resolveInsideCoordinateFontSize2(geometry);
905
+ if (fontSize === null) {
906
+ return [];
907
+ }
908
+ return [
909
+ ...geometry.insideFileLabels.map(
910
+ (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>`
911
+ ),
912
+ ...geometry.insideRankLabels.map(
913
+ (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>`
914
+ )
915
+ ];
916
+ }
917
+ var SvgRenderer = class {
918
+ async render(request) {
919
+ try {
920
+ const geometry = createBoardGeometry({
921
+ size: request.size,
922
+ padding: request.padding,
923
+ borderSize: request.borderSize,
924
+ flipped: request.flipped
925
+ });
926
+ const elements = [
927
+ `<rect x="0" y="0" width="${geometry.imageWidth}" height="${geometry.imageHeight}" fill="${escapeXml(request.colors.lightSquare)}" />`
928
+ ];
929
+ for (const square of SQUARES) {
930
+ const squareGeometry = geometry.squares[square];
931
+ elements.push(
932
+ `<rect x="${squareGeometry.x}" y="${squareGeometry.y}" width="${squareGeometry.size}" height="${squareGeometry.size}" fill="${escapeXml(isDarkSquare2(square) ? request.colors.darkSquare : request.colors.lightSquare)}" />`
933
+ );
934
+ }
935
+ for (const highlight of request.highlights) {
936
+ if (highlight.style !== "fill") {
937
+ continue;
938
+ }
939
+ const squareGeometry = geometry.squares[highlight.square];
940
+ elements.push(
941
+ `<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)}" />`
942
+ );
943
+ }
944
+ for (const highlight of request.highlights) {
945
+ if (highlight.style !== "circle") {
946
+ continue;
947
+ }
948
+ const squareGeometry = geometry.squares[highlight.square];
949
+ const lineWidth = resolveCircleLineWidth2(squareGeometry.size, highlight.lineWidth);
950
+ const radius = resolveCircleRadius2(squareGeometry.size, highlight.radius, lineWidth);
951
+ elements.push(
952
+ `<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)}" />`
953
+ );
954
+ }
955
+ elements.push(...renderCoordinates(request, geometry));
956
+ elements.push(...await renderPieces(request, geometry));
957
+ return [
958
+ `<svg xmlns="http://www.w3.org/2000/svg" width="${geometry.imageWidth}" height="${geometry.imageHeight}" viewBox="0 0 ${geometry.imageWidth} ${geometry.imageHeight}">`,
959
+ ...elements,
960
+ "</svg>"
961
+ ].join("");
962
+ } catch (error) {
963
+ if (error instanceof RenderError) {
964
+ throw error;
965
+ }
966
+ throw new RenderError("Failed to render chess board as SVG", { cause: error });
967
+ }
968
+ }
969
+ };
970
+
769
971
  // src/utils/io.ts
770
972
  import { writeFile } from "fs/promises";
771
973
  async function writeBufferToFile(filePath, buffer) {
@@ -775,6 +977,13 @@ async function writeBufferToFile(filePath, buffer) {
775
977
  throw new IOError(`Failed to write file: ${filePath}`, { cause: error });
776
978
  }
777
979
  }
980
+ async function writeStringToFile(filePath, contents) {
981
+ try {
982
+ await writeFile(filePath, contents, "utf8");
983
+ } catch (error) {
984
+ throw new IOError(`Failed to write file: ${filePath}`, { cause: error });
985
+ }
986
+ }
778
987
 
779
988
  // src/core/highlights.ts
780
989
  function normalizeHighlightEntries(input) {
@@ -1014,6 +1223,112 @@ function normalizeRenderInputs(options) {
1014
1223
  };
1015
1224
  }
1016
1225
 
1226
+ // src/core/parsers.ts
1227
+ import { Chess } from "chess.js";
1228
+ var PIECE_SYMBOL_TO_KEY = {
1229
+ K: "wK",
1230
+ Q: "wQ",
1231
+ R: "wR",
1232
+ B: "wB",
1233
+ N: "wN",
1234
+ P: "wP",
1235
+ k: "bK",
1236
+ q: "bQ",
1237
+ r: "bR",
1238
+ b: "bB",
1239
+ n: "bN",
1240
+ p: "bP"
1241
+ };
1242
+ function chessBoardToBoardArray(board) {
1243
+ return board.map(
1244
+ (rank) => rank.map((piece) => {
1245
+ if (!piece) {
1246
+ return null;
1247
+ }
1248
+ return piece.color === "w" ? piece.type.toUpperCase() : piece.type;
1249
+ })
1250
+ );
1251
+ }
1252
+ function parseFEN(fen) {
1253
+ const chess = new Chess();
1254
+ try {
1255
+ chess.load(fen);
1256
+ } catch (error) {
1257
+ throw new ParseError("Invalid FEN", { cause: error });
1258
+ }
1259
+ return parseBoardArray(chessBoardToBoardArray(chess.board()));
1260
+ }
1261
+ function parsePGN(pgn) {
1262
+ const chess = new Chess();
1263
+ try {
1264
+ chess.loadPgn(pgn);
1265
+ } catch (error) {
1266
+ throw new ParseError("Invalid PGN", { cause: error });
1267
+ }
1268
+ return parseBoardArray(chessBoardToBoardArray(chess.board()));
1269
+ }
1270
+ function parseBoardArray(board) {
1271
+ const validatedBoard = validateBoardArray(board);
1272
+ const position = createEmptyBoardPosition();
1273
+ validatedBoard.forEach((rank, rankIndex) => {
1274
+ rank.forEach((cell, fileIndex) => {
1275
+ if (cell === null) {
1276
+ return;
1277
+ }
1278
+ const pieceKey = PIECE_SYMBOL_TO_KEY[cell];
1279
+ if (!pieceKey) {
1280
+ throw new ValidationError(`Invalid board piece: ${cell}`);
1281
+ }
1282
+ const square = `${FILES[fileIndex]}${8 - rankIndex}`;
1283
+ position.squares[square] = pieceKey;
1284
+ });
1285
+ });
1286
+ return position;
1287
+ }
1288
+
1289
+ // src/api/render-request.ts
1290
+ function parseInputPosition(options) {
1291
+ if (typeof options.fen === "string") {
1292
+ return parseFEN(options.fen);
1293
+ }
1294
+ if (typeof options.pgn === "string") {
1295
+ return parsePGN(options.pgn);
1296
+ }
1297
+ if (Array.isArray(options.board)) {
1298
+ return parseBoardArray(options.board);
1299
+ }
1300
+ throw new ValidationError("Exactly one of fen, pgn, or board must be provided");
1301
+ }
1302
+ function validateSingleInputSource(options) {
1303
+ const provided = [
1304
+ typeof options.fen === "string" ? options.fen : void 0,
1305
+ typeof options.pgn === "string" ? options.pgn : void 0,
1306
+ Array.isArray(options.board) ? options.board : void 0
1307
+ ].filter((value) => value !== void 0);
1308
+ if (provided.length !== 1) {
1309
+ throw new ValidationError("Exactly one of fen, pgn, or board must be provided");
1310
+ }
1311
+ }
1312
+ function createRenderRequest(board, options) {
1313
+ const normalized = normalizeRenderInputs(options);
1314
+ return {
1315
+ board,
1316
+ theme: normalized.theme,
1317
+ highlights: normalized.highlights,
1318
+ size: normalized.size,
1319
+ padding: normalized.padding,
1320
+ borderSize: normalized.borderSize,
1321
+ flipped: normalized.flipped,
1322
+ colors: normalized.colors,
1323
+ coordinates: normalized.coordinates
1324
+ };
1325
+ }
1326
+ function createRenderRequestFromOptions(options) {
1327
+ validateSingleInputSource(options);
1328
+ const board = parseInputPosition(options);
1329
+ return createRenderRequest(board, options);
1330
+ }
1331
+
1017
1332
  // src/api/class-api.ts
1018
1333
  var ChessImageGenerator = class {
1019
1334
  position = null;
@@ -1047,65 +1362,70 @@ var ChessImageGenerator = class {
1047
1362
  throw new ValidationError("No board position loaded");
1048
1363
  }
1049
1364
  const renderer = new CanvasPngRenderer();
1050
- const normalized = normalizeRenderInputs({
1365
+ const request = createRenderRequest(this.position, {
1051
1366
  ...this.defaults,
1052
1367
  highlights: this.highlights,
1053
1368
  highlightSquares: void 0
1054
1369
  });
1055
- return renderer.render({
1056
- board: this.position,
1057
- theme: normalized.theme,
1058
- highlights: normalized.highlights,
1059
- size: normalized.size,
1060
- padding: normalized.padding,
1061
- borderSize: normalized.borderSize,
1062
- flipped: normalized.flipped,
1063
- colors: normalized.colors,
1064
- coordinates: normalized.coordinates
1065
- });
1370
+ return renderer.render(request);
1066
1371
  }
1067
1372
  async toFile(filePath) {
1068
1373
  const buffer = await this.toBuffer();
1069
1374
  await writeBufferToFile(filePath, buffer);
1070
1375
  }
1071
- };
1072
-
1073
- // src/api/functional-api.ts
1074
- function parseInputPosition(options) {
1075
- if (typeof options.fen === "string") {
1076
- return parseFEN(options.fen);
1376
+ async toSvg() {
1377
+ if (!this.position) {
1378
+ throw new ValidationError("No board position loaded");
1379
+ }
1380
+ const renderer = new SvgRenderer();
1381
+ const request = createRenderRequest(this.position, {
1382
+ ...this.defaults,
1383
+ highlights: this.highlights,
1384
+ highlightSquares: void 0
1385
+ });
1386
+ return renderer.render(request);
1077
1387
  }
1078
- if (typeof options.pgn === "string") {
1079
- return parsePGN(options.pgn);
1388
+ async toSvgFile(filePath) {
1389
+ await writeStringToFile(filePath, await this.toSvg());
1080
1390
  }
1081
- if (Array.isArray(options.board)) {
1082
- return parseBoardArray(options.board);
1391
+ async toJpeg() {
1392
+ if (!this.position) {
1393
+ throw new ValidationError("No board position loaded");
1394
+ }
1395
+ const renderer = new CanvasJpegRenderer();
1396
+ const request = createRenderRequest(this.position, {
1397
+ ...this.defaults,
1398
+ highlights: this.highlights,
1399
+ highlightSquares: void 0
1400
+ });
1401
+ return renderer.render(request);
1083
1402
  }
1084
- throw new ValidationError("Exactly one of fen, pgn, or board must be provided");
1085
- }
1086
- async function renderChess(options) {
1087
- const provided = [
1088
- typeof options.fen === "string" ? options.fen : void 0,
1089
- typeof options.pgn === "string" ? options.pgn : void 0,
1090
- Array.isArray(options.board) ? options.board : void 0
1091
- ].filter((value) => value !== void 0);
1092
- if (provided.length !== 1) {
1093
- throw new ValidationError("Exactly one of fen, pgn, or board must be provided");
1403
+ async toJpegFile(filePath) {
1404
+ await writeBufferToFile(filePath, await this.toJpeg());
1094
1405
  }
1095
- const position = parseInputPosition(options);
1406
+ };
1407
+
1408
+ // src/api/functional-api.ts
1409
+ async function renderChess(options) {
1096
1410
  const renderer = new CanvasPngRenderer();
1097
- const normalized = normalizeRenderInputs(options);
1098
- return renderer.render({
1099
- board: position,
1100
- theme: normalized.theme,
1101
- highlights: normalized.highlights,
1102
- size: normalized.size,
1103
- padding: normalized.padding,
1104
- borderSize: normalized.borderSize,
1105
- flipped: normalized.flipped,
1106
- colors: normalized.colors,
1107
- coordinates: normalized.coordinates
1108
- });
1411
+ return renderer.render(createRenderRequestFromOptions(options));
1412
+ }
1413
+ async function renderSvg(options) {
1414
+ const renderer = new SvgRenderer();
1415
+ return renderer.render(createRenderRequestFromOptions(options));
1416
+ }
1417
+ async function renderJpeg(options) {
1418
+ const renderer = new CanvasJpegRenderer();
1419
+ return renderer.render(createRenderRequestFromOptions(options));
1420
+ }
1421
+ async function renderFile(filePath, options) {
1422
+ await writeBufferToFile(filePath, await renderChess(options));
1423
+ }
1424
+ async function renderSvgFile(filePath, options) {
1425
+ await writeStringToFile(filePath, await renderSvg(options));
1426
+ }
1427
+ async function renderJpegFile(filePath, options) {
1428
+ await writeBufferToFile(filePath, await renderJpeg(options));
1109
1429
  }
1110
1430
  export {
1111
1431
  ChessImageGenerator,
@@ -1115,6 +1435,11 @@ export {
1115
1435
  ThemeError,
1116
1436
  ValidationError,
1117
1437
  registerTheme,
1118
- renderChess
1438
+ renderChess,
1439
+ renderFile,
1440
+ renderJpeg,
1441
+ renderJpegFile,
1442
+ renderSvg,
1443
+ renderSvgFile
1119
1444
  };
1120
1445
  //# sourceMappingURL=index.js.map