ansimax 1.2.5 → 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,155 @@
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
+
65
+ ## [1.2.6] — ASCII module improvements
66
+
67
+ Patch release focused on ASCII module quality and feature additions. No
68
+ breaking changes — every 1.2.x program runs identically.
69
+
70
+ ### Added — 4 new built-in ramps
71
+
72
+ `ASCII_RAMPS` now includes 4 additional ramps for different aesthetic styles:
73
+
74
+ ```js
75
+ import { ASCII_RAMPS, ascii } from 'ansimax';
76
+
77
+ ASCII_RAMPS.binary // ' █' — pure 2-char ramp
78
+ ASCII_RAMPS.dots // ' ⠁⠃⠇⠧⠷⡷⣷⣿' — Unicode braille (sparse aesthetic)
79
+ ASCII_RAMPS.shades // ' ⠁⠃⠇⠧⠷⡷⣷⣿█' — combined shading
80
+ ASCII_RAMPS.ascii64 // 64-char printable ASCII — non-Unicode terminals
81
+
82
+ ascii.fromImage(pixels, { ramp: 'shades' });
83
+ ascii.fromImage(pixels, { ramp: 'binary', bgColor: true }); // photo-like effect
84
+ ```
85
+
86
+ ### Added — `bgColor` option in `fromImage`
87
+
88
+ Render the source pixel's color as the **background** instead of the foreground.
89
+ Pairs especially well with `ramp: 'binary'` for a photo-like effect where each
90
+ character cell becomes a colored block:
91
+
92
+ ```js
93
+ ascii.fromImage(pixels, {
94
+ width: 80,
95
+ bgColor: true, // colors go on background
96
+ ramp: 'binary', // chars are spaces/blocks
97
+ });
98
+ ```
99
+
100
+ Implies `color: true` — no need to set both.
101
+
102
+ ### Added — `brightness` and `contrast` in `fromImage`
103
+
104
+ Pre-adjust the image's tonal range without modifying the source pixels:
105
+
106
+ ```js
107
+ ascii.fromImage(pixels, {
108
+ width: 80,
109
+ brightness: 0.2, // [-1, 1] — positive = lighter, negative = darker
110
+ contrast: 0.3, // [-1, 1] — positive = boosted, negative = flattened
111
+ });
112
+ ```
113
+
114
+ Useful for tuning hard-to-read photos without re-processing the source.
115
+ Values are clamped to `[-1, 1]`. Default `0` (no change, identical to v1.2.5).
116
+
117
+ ### Added — `kerning` option in `figletText`
118
+
119
+ Control horizontal spacing between FIGfont glyphs:
120
+
121
+ ```js
122
+ ascii.figletText('HELLO', font, {
123
+ kerning: 1, // 1-space gap between each character glyph
124
+ });
125
+ ```
126
+
127
+ Default `0` (touching glyphs, matches previous behavior).
128
+
129
+ ### Added — Multi-line `figletText`
130
+
131
+ `figletText` now handles `\n` in input — each line renders as a separate
132
+ FIGfont block:
133
+
134
+ ```js
135
+ ascii.figletText('LINE 1\nLINE 2', font, {
136
+ lineSpacing: 1, // 1 blank line between rendered lines
137
+ });
138
+ ```
139
+
140
+ Single-line text takes a fast path (no behavior change for v1.2.5 callers).
141
+
142
+ ### Improved
143
+
144
+ - Better JSDoc on `ASCII_RAMPS` with `@example` showing every ramp
145
+ - Brightness/contrast use standard photo-editing formulas (linear stretch around midpoint)
146
+ - All 1958 + 25 new tests pass
147
+
148
+ ### Notes
149
+
150
+ - No new runtime dependencies — still zero
151
+ - Drop-in replacement for `1.2.5`
152
+
153
+ ---
154
+
6
155
  ## [1.2.5] — Phase 3 closure: image-to-ASCII engine
7
156
 
8
157
  Minor release closing the **ASCII engine roadmap (Phase 3)** with five
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.5-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
 
@@ -49,7 +49,7 @@ Ansimax es una **librería de renderizado todo-en-uno** para construir interface
49
49
  npm install ansimax
50
50
  ```
51
51
 
52
- ```ts
52
+ ```js
53
53
  import { color, gradient, ascii, loader, sleep } from 'ansimax';
54
54
 
55
55
  console.log(ascii.banner('hola', {
@@ -122,7 +122,7 @@ yarn add ansimax
122
122
 
123
123
  ## ⚡ Ejemplo en 30 segundos
124
124
 
125
- ```ts
125
+ ```js
126
126
  import { color, gradient, loader, ascii, sleep } from 'ansimax';
127
127
 
128
128
  console.log(ascii.banner('deploy', {
@@ -140,7 +140,7 @@ console.log(color.green('✓') + ' Listo en ' + color.bold('1.4s'));
140
140
 
141
141
  ## 🚀 Inicio rápido
142
142
 
143
- ```ts
143
+ ```js
144
144
  import { configure, color, themes, gradient } from 'ansimax';
145
145
 
146
146
  // Configuración global
@@ -186,7 +186,7 @@ console.log(themes.primary('primary de cyberpunk'));
186
186
 
187
187
  <img src="media/colors.png" alt="Colores y gradientes" />
188
188
 
189
- ```ts
189
+ ```js
190
190
  import { color, gradient, rainbow } from 'ansimax';
191
191
 
192
192
  // Colores básicos
@@ -204,7 +204,7 @@ console.log(rainbow('preset rainbow integrado'));
204
204
 
205
205
  ### Gradientes animados (v1.2.0)
206
206
 
207
- ```ts
207
+ ```js
208
208
  import { animateGradient, sleep } from 'ansimax';
209
209
 
210
210
  // Animación de flujo de color — corre hasta llamar stop()
@@ -227,7 +227,7 @@ await animateGradient('¡Listo!', ['#50fa7b', '#bd93f9'], {
227
227
 
228
228
  <img src="media/easing_curves.png" alt="Colors and gradients" />
229
229
 
230
- ```ts
230
+ ```js
231
231
  import { gradient } from 'ansimax';
232
232
 
233
233
  const stops = ['#ff79c6', '#bd93f9', '#8be9fd'];
@@ -247,7 +247,7 @@ console.log(gradient('hola mundo', stops, { easing: (t) => t * t * t }));
247
247
 
248
248
  <img src="media/conic_gradients.png" alt="Colors and gradients" />
249
249
 
250
- ```ts
250
+ ```js
251
251
  import { gradientRect } from 'ansimax';
252
252
 
253
253
  // Barrido radial alrededor del centro — rueda arcoíris
@@ -262,7 +262,7 @@ console.log(gradientRect({
262
262
 
263
263
  ### Gradientes reusables (v1.2.3)
264
264
 
265
- ```ts
265
+ ```js
266
266
  import { createGradient, reverseGradient, ascii } from 'ansimax';
267
267
 
268
268
  // Pre-resuelve los stops de hex una vez — significativamente más rápido para uso repetido
@@ -294,7 +294,7 @@ for (let p = 0; p < 1; p += 0.05) {
294
294
 
295
295
  <img src="media/ascii_art.png" alt="ASCII art" />
296
296
 
297
- ```ts
297
+ ```js
298
298
  import { ascii, gradient } from 'ansimax';
299
299
 
300
300
  console.log(ascii.banner('HOLA', {
@@ -308,13 +308,28 @@ console.log(ascii.box('¡Caja arcoiris!', { padding: 1, borderStyle: 'rounded' }
308
308
 
309
309
  ### Imagen → ASCII (v1.2.5)
310
310
 
311
- ```ts
311
+ ```js
312
312
  import { ascii } from 'ansimax';
313
- import type { PixelGrid } from 'ansimax';
313
+ import sharp from 'sharp';
314
+
315
+ // Obtén píxeles RGB raw de cualquier librería de imágenes — ejemplo con `sharp`.
316
+ // Puedes usar jimp, pngjs, canvas, o cualquier decoder. Ansimax sigue sin deps.
317
+ const { data, info } = await sharp('./foto.png')
318
+ .raw()
319
+ .toBuffer({ resolveWithObject: true });
320
+
321
+ // Convierte el buffer RGB raw → PixelGrid (un array 2D de objetos { r, g, b })
322
+ const pixels = [];
323
+ for (let y = 0; y < info.height; y++) {
324
+ const row = [];
325
+ for (let x = 0; x < info.width; x++) {
326
+ const i = (y * info.width + x) * info.channels;
327
+ row.push({ r: data[i], g: data[i + 1], b: data[i + 2] });
328
+ }
329
+ pixels.push(row);
330
+ }
314
331
 
315
- // Obtén un PixelGrid de cualquier librería de imágenes (sharp, jimp, pngjs, etc.)
316
- // Cada Pixel es `{ r, g, b }` o null
317
- const pixels: PixelGrid = await loadImagePixels('./foto.png');
332
+ // Ahora usa ansimax varias formas:
318
333
 
319
334
  // Monocromo
320
335
  console.log(ascii.fromImage(pixels, { width: 80 }));
@@ -336,7 +351,7 @@ console.log(ascii.fromImage(pixels, {
336
351
  }));
337
352
 
338
353
  // Modo rostro para retratos (mejora contraste de tonos medios)
339
- console.log(ascii.fromImage(retratoPixels, {
354
+ console.log(ascii.fromImage(pixels, {
340
355
  width: 60,
341
356
  ramp: 'detailed',
342
357
  faceMode: true,
@@ -345,9 +360,9 @@ console.log(ascii.fromImage(retratoPixels, {
345
360
 
346
361
  ### Fuentes Figlet (v1.2.5)
347
362
 
348
- ```ts
363
+ ```js
349
364
  import { readFileSync } from 'node:fs';
350
- import { parseFiglet, ascii } from 'ansimax';
365
+ import { parseFiglet, ascii, gradient } from 'ansimax';
351
366
 
352
367
  // Descarga fuentes desde http://www.figlet.org/fontdb.cgi
353
368
  const font = parseFiglet(readFileSync('./standard.flf', 'utf8'));
@@ -364,7 +379,7 @@ console.log(ascii.figletText('STYLE', font, {
364
379
 
365
380
  <img src="media/trees.png" alt="Árboles" />
366
381
 
367
- ```ts
382
+ ```js
368
383
  import { tree, color } from 'ansimax';
369
384
 
370
385
  const proyecto = tree({ label: 'mi-app', icon: '📦', color: color.bold });
@@ -383,7 +398,7 @@ console.log(proyecto.render({
383
398
 
384
399
  <img src="media/pixel_art.png" alt="Pixel art" />
385
400
 
386
- ```ts
401
+ ```js
387
402
  import { images, createCanvas, gradientRect, SPRITES } from 'ansimax';
388
403
 
389
404
  // Sprite integrado
@@ -409,7 +424,7 @@ c.print();
409
424
 
410
425
  <img src="media/components.png" alt="Componentes" />
411
426
 
412
- ```ts
427
+ ```js
413
428
  import { components, color } from 'ansimax';
414
429
 
415
430
  console.log(components.table([
@@ -419,7 +434,7 @@ console.log(components.table([
419
434
  ['loaders', color.green('● listo'), '100%'],
420
435
  ], { borderStyle: 'rounded' }));
421
436
 
422
- console.log(components.badge('VERSION', 'v1.2.5'));
437
+ console.log(components.badge('VERSION', 'v1.2.7'));
423
438
  console.log(components.badge('BUILD', 'passing'));
424
439
  ```
425
440
 
@@ -427,7 +442,7 @@ console.log(components.badge('BUILD', 'passing'));
427
442
 
428
443
  <img src="media/timeline.png" alt="Timeline" />
429
444
 
430
- ```ts
445
+ ```js
431
446
  import { components } from 'ansimax';
432
447
 
433
448
  console.log(components.timeline([
@@ -440,7 +455,7 @@ console.log(components.timeline([
440
455
 
441
456
  ### Loaders y Progreso
442
457
 
443
- ```ts
458
+ ```js
444
459
  import { loader, sleep } from 'ansimax';
445
460
 
446
461
  // Spinner con éxito/fallo
@@ -469,7 +484,7 @@ await loader.tasks([
469
484
 
470
485
  ### Animaciones
471
486
 
472
- ```ts
487
+ ```js
473
488
  import { animate, gradient, sleep } from 'ansimax';
474
489
 
475
490
  await animate.typewriter('Bienvenido al wizard de deployment...', {
@@ -491,7 +506,7 @@ await animate.parallel([
491
506
 
492
507
  <img src="media/themes.png" alt="Temas" />
493
508
 
494
- ```ts
509
+ ```js
495
510
  import { themes, createTheme } from 'ansimax';
496
511
 
497
512
  // Temas built-in
@@ -580,7 +595,7 @@ node examples/all-in-one.cjs
580
595
 
581
596
  La config global afecta cada módulo que la respeta (colores, temas, velocidad de animación, etc.):
582
597
 
583
- ```ts
598
+ ```js
584
599
  import { configure, getConfig, withConfig, onConfigKeyChange } from 'ansimax';
585
600
 
586
601
  configure({
@@ -822,6 +837,59 @@ ansimax/
822
837
 
823
838
  ## 📝 Changelog
824
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
+
862
+ ### v1.2.6 — Mejoras del módulo ASCII
863
+
864
+ Release patch con mejoras al motor ASCII:
865
+
866
+ - 🎨 **4 ramps built-in nuevos** — `binary`, `dots`, `shades`, `ascii64`
867
+ - 🖼️ **Opción `bgColor`** en `fromImage` — colores van al background (excelente con `ramp: 'binary'` para efecto foto)
868
+ - 🔆 **`brightness` y `contrast`** pre-ajuste en `fromImage` — afina el rango tonal sin re-procesar
869
+ - 📏 **Opción `kerning`** en `figletText` — controla el espacio entre glyphs
870
+ - 📄 **`figletText` multi-línea** — input con `\n` renderiza múltiples bloques FIGfont con `lineSpacing` opcional
871
+
872
+ ```js
873
+ import { ascii, ASCII_RAMPS } from 'ansimax';
874
+
875
+ // Renderizado tipo-foto con bloques coloreados en background
876
+ console.log(ascii.fromImage(pixels, {
877
+ width: 80,
878
+ bgColor: true,
879
+ ramp: 'binary',
880
+ brightness: 0.1,
881
+ contrast: 0.2,
882
+ }));
883
+
884
+ // Figlet multi-línea con espaciado
885
+ console.log(ascii.figletText('TITULO\nSUBTITULO', font, {
886
+ kerning: 1,
887
+ lineSpacing: 1,
888
+ }));
889
+ ```
890
+
891
+ Drop-in replacement para `1.2.5`.
892
+
825
893
  ### v1.2.5 — Fase 3 completa: motor de imagen-a-ASCII
826
894
 
827
895
  Release minor que cierra el roadmap del motor ASCII con 5 features nuevas:
@@ -833,8 +901,16 @@ Release minor que cierra el roadmap del motor ASCII con 5 features nuevas:
833
901
  - 🔠 **Soporte Figlet (.flf)** — `parseFiglet()` + `ascii.figletText()` para 250+ fuentes de comunidad
834
902
  - ⚡ **Bonus: detección de bordes Sobel** — `edgeDetect: 'sobel'` para efectos line-art
835
903
 
836
- ```ts
837
- import { ascii } from 'ansimax';
904
+ ```js
905
+ import { readFileSync } from 'node:fs';
906
+ import { ascii, parseFiglet } from 'ansimax';
907
+
908
+ // Construye un PixelGrid pequeño a mano (usa sharp/jimp/etc para imágenes
909
+ // reales — ver sección Imagen → ASCII arriba)
910
+ const pixels = [
911
+ [{ r: 255, g: 0, b: 0 }, { r: 0, g: 255, b: 0 }],
912
+ [{ r: 0, g: 0, b: 255 }, { r: 255, g: 255, b: 0 }],
913
+ ];
838
914
 
839
915
  // Imagen a ASCII (input desde sharp/jimp/etc, sin dependencia de decoder)
840
916
  console.log(ascii.fromImage(pixels, {
@@ -859,7 +935,7 @@ Release patch añadiendo metadata de inspección y un helper `reverseGradient()`
859
935
  - 🔄 **Helper `reverseGradient()`** — invierte el orden de stops de un gradiente (funciona con arrays o `ReusableGradient`)
860
936
  - 🎯 **`presets` exportado con su nombre canónico** — junto al alias existente `colorPresets`
861
937
 
862
- ```ts
938
+ ```js
863
939
  import { createGradient, reverseGradient } from 'ansimax';
864
940
 
865
941
  const fire = createGradient(['#ff5555', '#ffb86c', '#f1fa8c']);
@@ -880,8 +956,8 @@ Release patch añadiendo una API orientada a performance:
880
956
  - 📖 Más JSDoc con ejemplos ejecutables
881
957
  - 🎯 Coincide con la firma `ColorFn` — funciona como `colorFn` en `ascii.banner`, themes, etc.
882
958
 
883
- ```ts
884
- import { createGradient } from 'ansimax';
959
+ ```js
960
+ import { createGradient, ascii } from 'ansimax';
885
961
 
886
962
  const fire = createGradient(['#ff5555', '#ffb86c', '#f1fa8c']);
887
963
  console.log(fire('¡Colores reusados!'));
@@ -910,7 +986,7 @@ Release minor que cierra el roadmap del motor de gradientes con tres features po
910
986
  - 📐 **Curvas de interpolación** — `linear` / `ease-in` / `ease-out` / `ease-in-out` / `cubic-bezier` / funciones personalizadas
911
987
  - ⭕ **Gradientes cónicos** — `gradientRect({ style: 'conic', startAngle })` para barridos radiales
912
988
 
913
- ```ts
989
+ ```js
914
990
  import { animateGradient } from 'ansimax';
915
991
 
916
992
  const ctrl = animateGradient('Cargando...', ['#ff79c6', '#bd93f9', '#8be9fd']);