ansimax 1.2.6 → 1.2.7

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/CHANGELOG.md CHANGED
@@ -3,6 +3,65 @@
3
3
  All notable changes to **ansimax** are documented in this file.
4
4
  This project follows [Semantic Versioning](https://semver.org/).
5
5
 
6
+ ## [1.2.7] — Bug fixes + robustness
7
+
8
+ Patch release focused on edge case handling, better error messages, and
9
+ defensive coding. No breaking changes — every 1.2.x program runs identically.
10
+
11
+ ### Fixed
12
+
13
+ - **`ascii.fromImage()` silently accepted `width: 0`, `NaN`, `Infinity`** —
14
+ now returns an empty string explicitly. Previously it would clamp to 1
15
+ and produce a single-character-wide output, which was confusing:
16
+
17
+ ```js
18
+ // Before (v1.2.6 and earlier):
19
+ ascii.fromImage(pixels, { width: 0 }); // → 1-char-wide output 😞
20
+ ascii.fromImage(pixels, { width: NaN }); // → 1-char-wide output 😞
21
+
22
+ // Now (v1.2.7):
23
+ ascii.fromImage(pixels, { width: 0 }); // → '' (explicit)
24
+ ascii.fromImage(pixels, { width: NaN }); // → ''
25
+ ascii.fromImage(pixels, { width: -10 }); // → ''
26
+ ascii.fromImage(pixels, { width: 1 }); // → still works, 1-char wide
27
+ ```
28
+
29
+ Same validation applies to `height` when explicitly set.
30
+
31
+ - **`ascii.figletText('', font)` returned `font.height - 1` empty lines**
32
+ instead of an empty string. Now returns `''` immediately.
33
+
34
+ - **`ascii.fromImage()` could crash on non-rectangular grids** (rows of
35
+ different widths) because `_resizePixels` assumed all rows had the same
36
+ length as the first row. Now each row is sampled by its actual width,
37
+ with missing pixels coalesced to `null` (the standard "transparent"
38
+ marker).
39
+
40
+ ### Improved — Error codes everywhere
41
+
42
+ Errors thrown by the ASCII module now carry stable `.code` properties for
43
+ programmatic catching:
44
+
45
+ | Function | Error condition | `.code` |
46
+ |---|---|---|
47
+ | `parseFiglet` | non-string / empty input | `ANSIMAX_INVALID_FIGLET_INPUT` |
48
+ | `parseFiglet` | unrecognized header | `ANSIMAX_INVALID_FIGLET_HEADER` |
49
+ | `parseFiglet` | non-positive height | `ANSIMAX_INVALID_FIGLET_HEIGHT` |
50
+ | `ascii.registerFont` | empty name | `ANSIMAX_INVALID_FONT_NAME` |
51
+ | `ascii.registerFont` | reserved name without `force` | `ANSIMAX_RESERVED_FONT_NAME` |
52
+
53
+ Error messages also include a debug snippet for `parseFiglet` header
54
+ errors (truncated at 60 chars), so you immediately see what was wrong.
55
+
56
+ ### Notes
57
+
58
+ - Error message text may have changed slightly — if you were matching exact
59
+ strings in tests, switch to `.code` checks (which are stable forever)
60
+ - All 1983 + 17 new tests pass
61
+ - No new runtime dependencies — still zero
62
+
63
+ ---
64
+
6
65
  ## [1.2.6] — ASCII module improvements
7
66
 
8
67
  Patch release focused on ASCII module quality and feature additions. No
package/README.es.md CHANGED
@@ -7,10 +7,10 @@
7
7
  _Colores • Gradientes • Animaciones • ASCII Art • Pixel Art • Árboles • Componentes • Temas_
8
8
 
9
9
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg?style=flat-square)](LICENSE)
10
- [![npm](https://img.shields.io/badge/npm-v1.2.6-cb3837.svg?style=flat-square)](https://www.npmjs.com/package/ansimax)
10
+ [![npm](https://img.shields.io/badge/npm-v1.2.7-cb3837.svg?style=flat-square)](https://www.npmjs.com/package/ansimax)
11
11
  [![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6.svg?style=flat-square)](tsconfig.json)
12
12
  [![Coverage](https://img.shields.io/badge/coverage-98%25-brightgreen.svg?style=flat-square)](#testing)
13
- [![Tests](https://img.shields.io/badge/tests-1700%2B%20passing-brightgreen.svg?style=flat-square)](#testing)
13
+ [![Tests](https://img.shields.io/badge/tests-1900%2B%20passing-brightgreen.svg?style=flat-square)](#testing)
14
14
  [![Zero deps](https://img.shields.io/badge/dependencies-0-brightgreen.svg?style=flat-square)](#)
15
15
  [![Bundle](https://img.shields.io/badge/bundle-%3C100kb-brightgreen.svg?style=flat-square)](#)
16
16
 
@@ -434,7 +434,7 @@ console.log(components.table([
434
434
  ['loaders', color.green('● listo'), '100%'],
435
435
  ], { borderStyle: 'rounded' }));
436
436
 
437
- console.log(components.badge('VERSION', 'v1.2.6'));
437
+ console.log(components.badge('VERSION', 'v1.2.7'));
438
438
  console.log(components.badge('BUILD', 'passing'));
439
439
  ```
440
440
 
@@ -837,6 +837,28 @@ ansimax/
837
837
 
838
838
  ## 📝 Changelog
839
839
 
840
+ ### v1.2.7 — Correcciones + robustez
841
+
842
+ Release patch enfocado en manejo de edge cases y mejor DX:
843
+
844
+ - 🐛 **`fromImage` rechaza dimensiones inválidas explícitamente** — `width: 0`, `NaN`, `Infinity` ahora retornan `''` en vez de clampear silenciosamente
845
+ - 🐛 **`figletText('')` retorna `''`** — antes retornaba filas vacías
846
+ - 🛡️ **Grids no-rectangulares manejados con gracia** — filas de distinto largo ya no crashean `fromImage`
847
+ - 🎯 **Códigos de error añadidos en todo el módulo ASCII** — `ANSIMAX_INVALID_FIGLET_HEADER`, `ANSIMAX_INVALID_FONT_NAME`, etc. para `catch` programático
848
+ - 📝 **Mejores mensajes de error** — `parseFiglet` ahora incluye snippet del input problemático
849
+
850
+ ```js
851
+ try {
852
+ ascii.registerFont('big', myFont); // 'big' está reservado
853
+ } catch (e) {
854
+ if (e.code === 'ANSIMAX_RESERVED_FONT_NAME') {
855
+ ascii.registerFont('big', myFont, { force: true }); // override
856
+ }
857
+ }
858
+ ```
859
+
860
+ Drop-in replacement para `1.2.6`.
861
+
840
862
  ### v1.2.6 — Mejoras del módulo ASCII
841
863
 
842
864
  Release patch con mejoras al motor ASCII:
package/README.md CHANGED
@@ -7,10 +7,10 @@
7
7
  _Colors • Gradients • Animations • ASCII Art • Pixel Art • Trees • Components • Themes_
8
8
 
9
9
  [![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg?style=flat-square)](LICENSE)
10
- [![npm](https://img.shields.io/badge/npm-v1.2.6-cb3837.svg?style=flat-square)](https://www.npmjs.com/package/ansimax)
10
+ [![npm](https://img.shields.io/badge/npm-v1.2.7-cb3837.svg?style=flat-square)](https://www.npmjs.com/package/ansimax)
11
11
  [![TypeScript](https://img.shields.io/badge/TypeScript-strict-3178c6.svg?style=flat-square)](tsconfig.json)
12
12
  [![Coverage](https://img.shields.io/badge/coverage-98%25-brightgreen.svg?style=flat-square)](#testing)
13
- [![Tests](https://img.shields.io/badge/tests-1700%2B%20passing-brightgreen.svg?style=flat-square)](#testing)
13
+ [![Tests](https://img.shields.io/badge/tests-1900%2B%20passing-brightgreen.svg?style=flat-square)](#testing)
14
14
  [![Zero deps](https://img.shields.io/badge/dependencies-0-brightgreen.svg?style=flat-square)](#)
15
15
  [![Bundle](https://img.shields.io/badge/bundle-%3C100kb-brightgreen.svg?style=flat-square)](#)
16
16
 
@@ -434,7 +434,7 @@ console.log(components.table([
434
434
  ['loaders', color.green('● ready'), '100%'],
435
435
  ], { borderStyle: 'rounded' }));
436
436
 
437
- console.log(components.badge('VERSION', 'v1.2.6'));
437
+ console.log(components.badge('VERSION', 'v1.2.7'));
438
438
  console.log(components.badge('BUILD', 'passing'));
439
439
  ```
440
440
 
@@ -837,6 +837,28 @@ ansimax/
837
837
 
838
838
  ## 📝 Changelog
839
839
 
840
+ ### v1.2.7 — Bug fixes + robustness
841
+
842
+ Patch release focused on edge case handling and better DX:
843
+
844
+ - 🐛 **`fromImage` rejects invalid dimensions explicitly** — `width: 0`, `NaN`, `Infinity` now return `''` instead of clamping silently
845
+ - 🐛 **`figletText('')` returns `''`** — previously returned empty rows
846
+ - 🛡️ **Non-rectangular grids handled gracefully** — rows of different widths no longer crash `fromImage`
847
+ - 🎯 **Error codes added throughout ASCII module** — `ANSIMAX_INVALID_FIGLET_HEADER`, `ANSIMAX_INVALID_FONT_NAME`, etc. for programmatic catch
848
+ - 📝 **Better error messages** — `parseFiglet` now includes a snippet of the problematic input
849
+
850
+ ```js
851
+ try {
852
+ ascii.registerFont('big', myFont); // 'big' is reserved
853
+ } catch (e) {
854
+ if (e.code === 'ANSIMAX_RESERVED_FONT_NAME') {
855
+ ascii.registerFont('big', myFont, { force: true }); // override
856
+ }
857
+ }
858
+ ```
859
+
860
+ Drop-in replacement for `1.2.6`.
861
+
840
862
  ### v1.2.6 — ASCII module improvements
841
863
 
842
864
  Patch release with ASCII engine improvements:
package/dist/index.js CHANGED
@@ -2346,12 +2346,16 @@ var validateFont = (name, fontMap) => {
2346
2346
  };
2347
2347
  var registerFont = (name, fontMap, opts = {}) => {
2348
2348
  if (typeof name !== "string" || !name.length) {
2349
- throw new TypeError("ascii.registerFont: name must be a non-empty string");
2349
+ const err = new TypeError("ascii.registerFont: name must be a non-empty string");
2350
+ err.code = "ANSIMAX_INVALID_FONT_NAME";
2351
+ throw err;
2350
2352
  }
2351
2353
  if (RESERVED_FONT_NAMES.has(name) && !opts.force) {
2352
- throw new Error(
2354
+ const err = new Error(
2353
2355
  `Font name "${name}" is reserved. Pass { force: true } to override.`
2354
2356
  );
2357
+ err.code = "ANSIMAX_RESERVED_FONT_NAME";
2358
+ throw err;
2355
2359
  }
2356
2360
  const safeMap = ensureFontMap(fontMap, name);
2357
2361
  validateFont(name, safeMap);
@@ -2604,10 +2608,15 @@ var _resizePixels = (pixels, targetW, targetH) => {
2604
2608
  for (let y = 0; y < targetH; y++) {
2605
2609
  const sy = Math.min(srcH - 1, Math.floor(y / targetH * srcH));
2606
2610
  const srcRow = pixels[sy];
2611
+ const actualRowW = Array.isArray(srcRow) ? srcRow.length : 0;
2607
2612
  const newRow = new Array(targetW);
2608
2613
  for (let x = 0; x < targetW; x++) {
2609
- const sx = Math.min(srcW - 1, Math.floor(x / targetW * srcW));
2610
- newRow[x] = srcRow[sx];
2614
+ if (actualRowW === 0) {
2615
+ newRow[x] = null;
2616
+ continue;
2617
+ }
2618
+ const sx = Math.min(actualRowW - 1, Math.floor(x / targetW * actualRowW));
2619
+ newRow[x] = srcRow[sx] ?? null;
2611
2620
  }
2612
2621
  out.push(newRow);
2613
2622
  }
@@ -2646,8 +2655,12 @@ var fromImage = (pixels, opts = {}) => {
2646
2655
  if (!Array.isArray(pixels) || pixels.length === 0) return "";
2647
2656
  const firstRow = pixels[0];
2648
2657
  if (!Array.isArray(firstRow) || firstRow.length === 0) return "";
2658
+ const requestedW = opts.width ?? 80;
2659
+ if (!Number.isFinite(requestedW) || requestedW <= 0) return "";
2660
+ if (opts.height !== void 0) {
2661
+ if (!Number.isFinite(opts.height) || opts.height <= 0) return "";
2662
+ }
2649
2663
  const {
2650
- width = 80,
2651
2664
  ramp = "standard",
2652
2665
  invert = false,
2653
2666
  dither = "none",
@@ -2662,7 +2675,7 @@ var fromImage = (pixels, opts = {}) => {
2662
2675
  } = opts;
2663
2676
  const srcH = pixels.length;
2664
2677
  const srcW = pixels[0].length;
2665
- const safeW = Math.max(1, Math.floor(width));
2678
+ const safeW = Math.max(1, Math.floor(requestedW));
2666
2679
  const computedH = Math.max(1, Math.round(srcH / srcW * safeW * 0.5));
2667
2680
  const safeH = opts.height != null ? Math.max(1, Math.floor(opts.height)) : computedH;
2668
2681
  const resized = _resizePixels(pixels, safeW, safeH);
@@ -2721,7 +2734,9 @@ var fromImage = (pixels, opts = {}) => {
2721
2734
  };
2722
2735
  var parseFiglet = (flfContent) => {
2723
2736
  if (typeof flfContent !== "string" || flfContent.length === 0) {
2724
- throw new TypeError("parseFiglet: input must be a non-empty string");
2737
+ const err = new TypeError("parseFiglet: input must be a non-empty string");
2738
+ err.code = "ANSIMAX_INVALID_FIGLET_INPUT";
2739
+ throw err;
2725
2740
  }
2726
2741
  const lines = flfContent.split(/\r?\n/);
2727
2742
  if (lines.length === 0) {
@@ -2730,13 +2745,20 @@ var parseFiglet = (flfContent) => {
2730
2745
  const header = lines[0];
2731
2746
  const m = /^flf2.\s*(\S)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(\d+)/.exec(header);
2732
2747
  if (!m) {
2733
- throw new TypeError('parseFiglet: invalid FIGfont header (expected "flf2a$..." line)');
2748
+ const snippet = header.length > 60 ? header.slice(0, 60) + "\u2026" : header;
2749
+ const err = new TypeError(
2750
+ `parseFiglet: invalid FIGfont header. Expected "flf2a$..." prefix, got: "${snippet}"`
2751
+ );
2752
+ err.code = "ANSIMAX_INVALID_FIGLET_HEADER";
2753
+ throw err;
2734
2754
  }
2735
2755
  const hardblank = m[1];
2736
2756
  const height = parseInt(m[2], 10);
2737
2757
  const commentLines = parseInt(m[6], 10);
2738
2758
  if (!Number.isFinite(height) || height <= 0) {
2739
- throw new TypeError(`parseFiglet: invalid height ${m[2]}`);
2759
+ const err = new TypeError(`parseFiglet: invalid height ${m[2]} (must be positive integer)`);
2760
+ err.code = "ANSIMAX_INVALID_FIGLET_HEIGHT";
2761
+ throw err;
2740
2762
  }
2741
2763
  let cursor2 = 1 + Math.max(0, commentLines);
2742
2764
  const glyphs = /* @__PURE__ */ new Map();
@@ -2759,6 +2781,7 @@ var parseFiglet = (flfContent) => {
2759
2781
  };
2760
2782
  var figletText = (text, font, opts = {}) => {
2761
2783
  if (typeof text !== "string") return "";
2784
+ if (text.length === 0) return "";
2762
2785
  if (!font || !font.glyphs || font.height <= 0) return "";
2763
2786
  const {
2764
2787
  trim = true,
package/dist/index.mjs CHANGED
@@ -2166,12 +2166,16 @@ var validateFont = (name, fontMap) => {
2166
2166
  };
2167
2167
  var registerFont = (name, fontMap, opts = {}) => {
2168
2168
  if (typeof name !== "string" || !name.length) {
2169
- throw new TypeError("ascii.registerFont: name must be a non-empty string");
2169
+ const err = new TypeError("ascii.registerFont: name must be a non-empty string");
2170
+ err.code = "ANSIMAX_INVALID_FONT_NAME";
2171
+ throw err;
2170
2172
  }
2171
2173
  if (RESERVED_FONT_NAMES.has(name) && !opts.force) {
2172
- throw new Error(
2174
+ const err = new Error(
2173
2175
  `Font name "${name}" is reserved. Pass { force: true } to override.`
2174
2176
  );
2177
+ err.code = "ANSIMAX_RESERVED_FONT_NAME";
2178
+ throw err;
2175
2179
  }
2176
2180
  const safeMap = ensureFontMap(fontMap, name);
2177
2181
  validateFont(name, safeMap);
@@ -2424,10 +2428,15 @@ var _resizePixels = (pixels, targetW, targetH) => {
2424
2428
  for (let y = 0; y < targetH; y++) {
2425
2429
  const sy = Math.min(srcH - 1, Math.floor(y / targetH * srcH));
2426
2430
  const srcRow = pixels[sy];
2431
+ const actualRowW = Array.isArray(srcRow) ? srcRow.length : 0;
2427
2432
  const newRow = new Array(targetW);
2428
2433
  for (let x = 0; x < targetW; x++) {
2429
- const sx = Math.min(srcW - 1, Math.floor(x / targetW * srcW));
2430
- newRow[x] = srcRow[sx];
2434
+ if (actualRowW === 0) {
2435
+ newRow[x] = null;
2436
+ continue;
2437
+ }
2438
+ const sx = Math.min(actualRowW - 1, Math.floor(x / targetW * actualRowW));
2439
+ newRow[x] = srcRow[sx] ?? null;
2431
2440
  }
2432
2441
  out.push(newRow);
2433
2442
  }
@@ -2466,8 +2475,12 @@ var fromImage = (pixels, opts = {}) => {
2466
2475
  if (!Array.isArray(pixels) || pixels.length === 0) return "";
2467
2476
  const firstRow = pixels[0];
2468
2477
  if (!Array.isArray(firstRow) || firstRow.length === 0) return "";
2478
+ const requestedW = opts.width ?? 80;
2479
+ if (!Number.isFinite(requestedW) || requestedW <= 0) return "";
2480
+ if (opts.height !== void 0) {
2481
+ if (!Number.isFinite(opts.height) || opts.height <= 0) return "";
2482
+ }
2469
2483
  const {
2470
- width = 80,
2471
2484
  ramp = "standard",
2472
2485
  invert = false,
2473
2486
  dither = "none",
@@ -2482,7 +2495,7 @@ var fromImage = (pixels, opts = {}) => {
2482
2495
  } = opts;
2483
2496
  const srcH = pixels.length;
2484
2497
  const srcW = pixels[0].length;
2485
- const safeW = Math.max(1, Math.floor(width));
2498
+ const safeW = Math.max(1, Math.floor(requestedW));
2486
2499
  const computedH = Math.max(1, Math.round(srcH / srcW * safeW * 0.5));
2487
2500
  const safeH = opts.height != null ? Math.max(1, Math.floor(opts.height)) : computedH;
2488
2501
  const resized = _resizePixels(pixels, safeW, safeH);
@@ -2541,7 +2554,9 @@ var fromImage = (pixels, opts = {}) => {
2541
2554
  };
2542
2555
  var parseFiglet = (flfContent) => {
2543
2556
  if (typeof flfContent !== "string" || flfContent.length === 0) {
2544
- throw new TypeError("parseFiglet: input must be a non-empty string");
2557
+ const err = new TypeError("parseFiglet: input must be a non-empty string");
2558
+ err.code = "ANSIMAX_INVALID_FIGLET_INPUT";
2559
+ throw err;
2545
2560
  }
2546
2561
  const lines = flfContent.split(/\r?\n/);
2547
2562
  if (lines.length === 0) {
@@ -2550,13 +2565,20 @@ var parseFiglet = (flfContent) => {
2550
2565
  const header = lines[0];
2551
2566
  const m = /^flf2.\s*(\S)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(\d+)/.exec(header);
2552
2567
  if (!m) {
2553
- throw new TypeError('parseFiglet: invalid FIGfont header (expected "flf2a$..." line)');
2568
+ const snippet = header.length > 60 ? header.slice(0, 60) + "\u2026" : header;
2569
+ const err = new TypeError(
2570
+ `parseFiglet: invalid FIGfont header. Expected "flf2a$..." prefix, got: "${snippet}"`
2571
+ );
2572
+ err.code = "ANSIMAX_INVALID_FIGLET_HEADER";
2573
+ throw err;
2554
2574
  }
2555
2575
  const hardblank = m[1];
2556
2576
  const height = parseInt(m[2], 10);
2557
2577
  const commentLines = parseInt(m[6], 10);
2558
2578
  if (!Number.isFinite(height) || height <= 0) {
2559
- throw new TypeError(`parseFiglet: invalid height ${m[2]}`);
2579
+ const err = new TypeError(`parseFiglet: invalid height ${m[2]} (must be positive integer)`);
2580
+ err.code = "ANSIMAX_INVALID_FIGLET_HEIGHT";
2581
+ throw err;
2560
2582
  }
2561
2583
  let cursor2 = 1 + Math.max(0, commentLines);
2562
2584
  const glyphs = /* @__PURE__ */ new Map();
@@ -2579,6 +2601,7 @@ var parseFiglet = (flfContent) => {
2579
2601
  };
2580
2602
  var figletText = (text, font, opts = {}) => {
2581
2603
  if (typeof text !== "string") return "";
2604
+ if (text.length === 0) return "";
2582
2605
  if (!font || !font.glyphs || font.height <= 0) return "";
2583
2606
  const {
2584
2607
  trim = true,
@@ -118,7 +118,7 @@ async function main() {
118
118
  console.log(components.section('🏷️ Badges & Status', { width: 60 }));
119
119
  console.log();
120
120
  console.log(' ',
121
- components.badge('VERSION', 'v1.2.6'),
121
+ components.badge('VERSION', 'v1.2.7'),
122
122
  components.badge('BUILD', 'passing'),
123
123
  components.badge('LICENSE', 'Apache 2.0'));
124
124
  console.log();
@@ -117,7 +117,7 @@ console.log();
117
117
  console.log(components.section('🏷️ Badges & Status', { width: 60 }));
118
118
  console.log();
119
119
  console.log(' ',
120
- components.badge('VERSION', 'v1.2.6'),
120
+ components.badge('VERSION', 'v1.2.7'),
121
121
  components.badge('BUILD', 'passing'),
122
122
  components.badge('LICENSE', 'Apache 2.0'));
123
123
  console.log();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ansimax",
3
- "version": "1.2.6",
3
+ "version": "1.2.7",
4
4
  "description": "Zero-dependency CLI rendering library: colors, gradients, animations, ASCII art, pixel art, components, and themes \u2014 all in TypeScript.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",