ansimax 1.2.5 → 1.2.6

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,96 @@
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.6] — ASCII module improvements
7
+
8
+ Patch release focused on ASCII module quality and feature additions. No
9
+ breaking changes — every 1.2.x program runs identically.
10
+
11
+ ### Added — 4 new built-in ramps
12
+
13
+ `ASCII_RAMPS` now includes 4 additional ramps for different aesthetic styles:
14
+
15
+ ```js
16
+ import { ASCII_RAMPS, ascii } from 'ansimax';
17
+
18
+ ASCII_RAMPS.binary // ' █' — pure 2-char ramp
19
+ ASCII_RAMPS.dots // ' ⠁⠃⠇⠧⠷⡷⣷⣿' — Unicode braille (sparse aesthetic)
20
+ ASCII_RAMPS.shades // ' ⠁⠃⠇⠧⠷⡷⣷⣿█' — combined shading
21
+ ASCII_RAMPS.ascii64 // 64-char printable ASCII — non-Unicode terminals
22
+
23
+ ascii.fromImage(pixels, { ramp: 'shades' });
24
+ ascii.fromImage(pixels, { ramp: 'binary', bgColor: true }); // photo-like effect
25
+ ```
26
+
27
+ ### Added — `bgColor` option in `fromImage`
28
+
29
+ Render the source pixel's color as the **background** instead of the foreground.
30
+ Pairs especially well with `ramp: 'binary'` for a photo-like effect where each
31
+ character cell becomes a colored block:
32
+
33
+ ```js
34
+ ascii.fromImage(pixels, {
35
+ width: 80,
36
+ bgColor: true, // colors go on background
37
+ ramp: 'binary', // chars are spaces/blocks
38
+ });
39
+ ```
40
+
41
+ Implies `color: true` — no need to set both.
42
+
43
+ ### Added — `brightness` and `contrast` in `fromImage`
44
+
45
+ Pre-adjust the image's tonal range without modifying the source pixels:
46
+
47
+ ```js
48
+ ascii.fromImage(pixels, {
49
+ width: 80,
50
+ brightness: 0.2, // [-1, 1] — positive = lighter, negative = darker
51
+ contrast: 0.3, // [-1, 1] — positive = boosted, negative = flattened
52
+ });
53
+ ```
54
+
55
+ Useful for tuning hard-to-read photos without re-processing the source.
56
+ Values are clamped to `[-1, 1]`. Default `0` (no change, identical to v1.2.5).
57
+
58
+ ### Added — `kerning` option in `figletText`
59
+
60
+ Control horizontal spacing between FIGfont glyphs:
61
+
62
+ ```js
63
+ ascii.figletText('HELLO', font, {
64
+ kerning: 1, // 1-space gap between each character glyph
65
+ });
66
+ ```
67
+
68
+ Default `0` (touching glyphs, matches previous behavior).
69
+
70
+ ### Added — Multi-line `figletText`
71
+
72
+ `figletText` now handles `\n` in input — each line renders as a separate
73
+ FIGfont block:
74
+
75
+ ```js
76
+ ascii.figletText('LINE 1\nLINE 2', font, {
77
+ lineSpacing: 1, // 1 blank line between rendered lines
78
+ });
79
+ ```
80
+
81
+ Single-line text takes a fast path (no behavior change for v1.2.5 callers).
82
+
83
+ ### Improved
84
+
85
+ - Better JSDoc on `ASCII_RAMPS` with `@example` showing every ramp
86
+ - Brightness/contrast use standard photo-editing formulas (linear stretch around midpoint)
87
+ - All 1958 + 25 new tests pass
88
+
89
+ ### Notes
90
+
91
+ - No new runtime dependencies — still zero
92
+ - Drop-in replacement for `1.2.5`
93
+
94
+ ---
95
+
6
96
  ## [1.2.5] — Phase 3 closure: image-to-ASCII engine
7
97
 
8
98
  Minor release closing the **ASCII engine roadmap (Phase 3)** with five
package/README.es.md CHANGED
@@ -7,7 +7,7 @@
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.6-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
13
  [![Tests](https://img.shields.io/badge/tests-1700%2B%20passing-brightgreen.svg?style=flat-square)](#testing)
@@ -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.6'));
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,37 @@ ansimax/
822
837
 
823
838
  ## 📝 Changelog
824
839
 
840
+ ### v1.2.6 — Mejoras del módulo ASCII
841
+
842
+ Release patch con mejoras al motor ASCII:
843
+
844
+ - 🎨 **4 ramps built-in nuevos** — `binary`, `dots`, `shades`, `ascii64`
845
+ - 🖼️ **Opción `bgColor`** en `fromImage` — colores van al background (excelente con `ramp: 'binary'` para efecto foto)
846
+ - 🔆 **`brightness` y `contrast`** pre-ajuste en `fromImage` — afina el rango tonal sin re-procesar
847
+ - 📏 **Opción `kerning`** en `figletText` — controla el espacio entre glyphs
848
+ - 📄 **`figletText` multi-línea** — input con `\n` renderiza múltiples bloques FIGfont con `lineSpacing` opcional
849
+
850
+ ```js
851
+ import { ascii, ASCII_RAMPS } from 'ansimax';
852
+
853
+ // Renderizado tipo-foto con bloques coloreados en background
854
+ console.log(ascii.fromImage(pixels, {
855
+ width: 80,
856
+ bgColor: true,
857
+ ramp: 'binary',
858
+ brightness: 0.1,
859
+ contrast: 0.2,
860
+ }));
861
+
862
+ // Figlet multi-línea con espaciado
863
+ console.log(ascii.figletText('TITULO\nSUBTITULO', font, {
864
+ kerning: 1,
865
+ lineSpacing: 1,
866
+ }));
867
+ ```
868
+
869
+ Drop-in replacement para `1.2.5`.
870
+
825
871
  ### v1.2.5 — Fase 3 completa: motor de imagen-a-ASCII
826
872
 
827
873
  Release minor que cierra el roadmap del motor ASCII con 5 features nuevas:
@@ -833,8 +879,16 @@ Release minor que cierra el roadmap del motor ASCII con 5 features nuevas:
833
879
  - 🔠 **Soporte Figlet (.flf)** — `parseFiglet()` + `ascii.figletText()` para 250+ fuentes de comunidad
834
880
  - ⚡ **Bonus: detección de bordes Sobel** — `edgeDetect: 'sobel'` para efectos line-art
835
881
 
836
- ```ts
837
- import { ascii } from 'ansimax';
882
+ ```js
883
+ import { readFileSync } from 'node:fs';
884
+ import { ascii, parseFiglet } from 'ansimax';
885
+
886
+ // Construye un PixelGrid pequeño a mano (usa sharp/jimp/etc para imágenes
887
+ // reales — ver sección Imagen → ASCII arriba)
888
+ const pixels = [
889
+ [{ r: 255, g: 0, b: 0 }, { r: 0, g: 255, b: 0 }],
890
+ [{ r: 0, g: 0, b: 255 }, { r: 255, g: 255, b: 0 }],
891
+ ];
838
892
 
839
893
  // Imagen a ASCII (input desde sharp/jimp/etc, sin dependencia de decoder)
840
894
  console.log(ascii.fromImage(pixels, {
@@ -859,7 +913,7 @@ Release patch añadiendo metadata de inspección y un helper `reverseGradient()`
859
913
  - 🔄 **Helper `reverseGradient()`** — invierte el orden de stops de un gradiente (funciona con arrays o `ReusableGradient`)
860
914
  - 🎯 **`presets` exportado con su nombre canónico** — junto al alias existente `colorPresets`
861
915
 
862
- ```ts
916
+ ```js
863
917
  import { createGradient, reverseGradient } from 'ansimax';
864
918
 
865
919
  const fire = createGradient(['#ff5555', '#ffb86c', '#f1fa8c']);
@@ -880,8 +934,8 @@ Release patch añadiendo una API orientada a performance:
880
934
  - 📖 Más JSDoc con ejemplos ejecutables
881
935
  - 🎯 Coincide con la firma `ColorFn` — funciona como `colorFn` en `ascii.banner`, themes, etc.
882
936
 
883
- ```ts
884
- import { createGradient } from 'ansimax';
937
+ ```js
938
+ import { createGradient, ascii } from 'ansimax';
885
939
 
886
940
  const fire = createGradient(['#ff5555', '#ffb86c', '#f1fa8c']);
887
941
  console.log(fire('¡Colores reusados!'));
@@ -910,7 +964,7 @@ Release minor que cierra el roadmap del motor de gradientes con tres features po
910
964
  - 📐 **Curvas de interpolación** — `linear` / `ease-in` / `ease-out` / `ease-in-out` / `cubic-bezier` / funciones personalizadas
911
965
  - ⭕ **Gradientes cónicos** — `gradientRect({ style: 'conic', startAngle })` para barridos radiales
912
966
 
913
- ```ts
967
+ ```js
914
968
  import { animateGradient } from 'ansimax';
915
969
 
916
970
  const ctrl = animateGradient('Cargando...', ['#ff79c6', '#bd93f9', '#8be9fd']);
package/README.md CHANGED
@@ -7,7 +7,7 @@
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.5-cb3837.svg?style=flat-square)](https://www.npmjs.com/package/ansimax)
10
+ [![npm](https://img.shields.io/badge/npm-v1.2.6-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
13
  [![Tests](https://img.shields.io/badge/tests-1700%2B%20passing-brightgreen.svg?style=flat-square)](#testing)
@@ -49,7 +49,7 @@ Ansimax is a **batteries-included rendering library** for building beautiful ter
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('hello', {
@@ -122,7 +122,7 @@ yarn add ansimax
122
122
 
123
123
  ## ⚡ 30-second example
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('✓') + ' Ready in ' + color.bold('1.4s'));
140
140
 
141
141
  ## 🚀 Quick Start
142
142
 
143
- ```ts
143
+ ```js
144
144
  import { configure, color, themes, gradient } from 'ansimax';
145
145
 
146
146
  // Global configuration
@@ -186,7 +186,7 @@ console.log(themes.primary('cyberpunk primary'));
186
186
 
187
187
  <img src="media/colors.png" alt="Colors and gradients" />
188
188
 
189
- ```ts
189
+ ```js
190
190
  import { color, gradient, rainbow } from 'ansimax';
191
191
 
192
192
  // Basic colors
@@ -204,7 +204,7 @@ console.log(rainbow('built-in rainbow preset'));
204
204
 
205
205
  ### Animated Gradients (v1.2.0)
206
206
 
207
- ```ts
207
+ ```js
208
208
  import { animateGradient, sleep } from 'ansimax';
209
209
 
210
210
  // Color flow animation — runs until you call stop()
@@ -227,7 +227,7 @@ await animateGradient('Done!', ['#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('hello world', 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
  // Radial sweep around center — rainbow wheel
@@ -262,7 +262,7 @@ console.log(gradientRect({
262
262
 
263
263
  ### Reusable Gradients (v1.2.3)
264
264
 
265
- ```ts
265
+ ```js
266
266
  import { createGradient, reverseGradient, ascii } from 'ansimax';
267
267
 
268
268
  // Pre-resolve hex stops once — significantly faster for repeated use
@@ -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('HELLO', {
@@ -308,13 +308,28 @@ console.log(ascii.box('Rainbow box!', { padding: 1, borderStyle: 'rounded' }));
308
308
 
309
309
  ### Image → 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
+ // Get raw RGB pixels from any image library — example using `sharp`.
316
+ // You can use jimp, pngjs, canvas, or any decoder. Ansimax stays zero-deps.
317
+ const { data, info } = await sharp('./photo.png')
318
+ .raw()
319
+ .toBuffer({ resolveWithObject: true });
320
+
321
+ // Convert raw RGB buffer → PixelGrid (a 2D array of { r, g, b } objects)
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
- // Get a PixelGrid from any image library (sharp, jimp, pngjs, etc.)
316
- // Each Pixel is `{ r, g, b }` or null
317
- const pixels: PixelGrid = await loadImagePixels('./photo.png');
332
+ // Now use ansimax multiple ways:
318
333
 
319
334
  // Monochrome
320
335
  console.log(ascii.fromImage(pixels, { width: 80 }));
@@ -336,7 +351,7 @@ console.log(ascii.fromImage(pixels, {
336
351
  }));
337
352
 
338
353
  // Face mode for portraits (boosts midtone contrast)
339
- console.log(ascii.fromImage(portraitPixels, {
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(portraitPixels, {
345
360
 
346
361
  ### Figlet Fonts (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
  // Download fonts from 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="Trees" />
366
381
 
367
- ```ts
382
+ ```js
368
383
  import { tree, color } from 'ansimax';
369
384
 
370
385
  const project = tree({ label: 'my-app', icon: '📦', color: color.bold });
@@ -383,7 +398,7 @@ console.log(project.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
  // Built-in sprite
@@ -409,7 +424,7 @@ c.print();
409
424
 
410
425
  <img src="media/components.png" alt="Components" />
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('● ready'), '100%'],
420
435
  ], { borderStyle: 'rounded' }));
421
436
 
422
- console.log(components.badge('VERSION', 'v1.2.5'));
437
+ console.log(components.badge('VERSION', 'v1.2.6'));
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 & Progress
442
457
 
443
- ```ts
458
+ ```js
444
459
  import { loader, sleep } from 'ansimax';
445
460
 
446
461
  // Spinner with success/failure
@@ -469,7 +484,7 @@ await loader.tasks([
469
484
 
470
485
  ### Animations
471
486
 
472
- ```ts
487
+ ```js
473
488
  import { animate, gradient, sleep } from 'ansimax';
474
489
 
475
490
  await animate.typewriter('Welcome to the deployment wizard...', {
@@ -491,7 +506,7 @@ await animate.parallel([
491
506
 
492
507
  <img src="media/themes.png" alt="Themes" />
493
508
 
494
- ```ts
509
+ ```js
495
510
  import { themes, createTheme } from 'ansimax';
496
511
 
497
512
  // Built-in themes
@@ -580,7 +595,7 @@ node examples/all-in-one.cjs
580
595
 
581
596
  Global config affects every module that respects it (colors, themes, animation speed, etc.):
582
597
 
583
- ```ts
598
+ ```js
584
599
  import { configure, getConfig, withConfig, onConfigKeyChange } from 'ansimax';
585
600
 
586
601
  configure({
@@ -822,6 +837,37 @@ ansimax/
822
837
 
823
838
  ## 📝 Changelog
824
839
 
840
+ ### v1.2.6 — ASCII module improvements
841
+
842
+ Patch release with ASCII engine improvements:
843
+
844
+ - 🎨 **4 new built-in ramps** — `binary`, `dots`, `shades`, `ascii64`
845
+ - 🖼️ **`bgColor` option** in `fromImage` — colors go on background (great with `ramp: 'binary'` for photo effect)
846
+ - 🔆 **`brightness` and `contrast`** pre-adjustment in `fromImage` — tune tonal range without re-processing
847
+ - 📏 **`kerning` option** in `figletText` — control space between glyphs
848
+ - 📄 **Multi-line `figletText`** — input with `\n` renders multiple FIGfont blocks with optional `lineSpacing`
849
+
850
+ ```js
851
+ import { ascii, ASCII_RAMPS } from 'ansimax';
852
+
853
+ // Photo-like rendering with colored background blocks
854
+ console.log(ascii.fromImage(pixels, {
855
+ width: 80,
856
+ bgColor: true,
857
+ ramp: 'binary',
858
+ brightness: 0.1,
859
+ contrast: 0.2,
860
+ }));
861
+
862
+ // Multi-line figlet with spacing
863
+ console.log(ascii.figletText('TITLE\nSUBTITLE', font, {
864
+ kerning: 1,
865
+ lineSpacing: 1,
866
+ }));
867
+ ```
868
+
869
+ Drop-in replacement for `1.2.5`.
870
+
825
871
  ### v1.2.5 — Phase 3 complete: image-to-ASCII engine
826
872
 
827
873
  Minor release closing the ASCII engine roadmap with 5 new features:
@@ -833,8 +879,16 @@ Minor release closing the ASCII engine roadmap with 5 new features:
833
879
  - 🔠 **Figlet (.flf) support** — `parseFiglet()` + `ascii.figletText()` for 250+ community fonts
834
880
  - ⚡ **Bonus: Sobel edge detection** — `edgeDetect: 'sobel'` for line-art effects
835
881
 
836
- ```ts
837
- import { ascii } from 'ansimax';
882
+ ```js
883
+ import { readFileSync } from 'node:fs';
884
+ import { ascii, parseFiglet } from 'ansimax';
885
+
886
+ // Build a small PixelGrid by hand (use sharp/jimp/etc for real images —
887
+ // see Image → ASCII section above)
888
+ const pixels = [
889
+ [{ r: 255, g: 0, b: 0 }, { r: 0, g: 255, b: 0 }],
890
+ [{ r: 0, g: 0, b: 255 }, { r: 255, g: 255, b: 0 }],
891
+ ];
838
892
 
839
893
  // Image to ASCII (input from sharp/jimp/etc, no decoder dependency)
840
894
  console.log(ascii.fromImage(pixels, {
@@ -859,7 +913,7 @@ Patch release adding inspection metadata and a `reverseGradient()` helper:
859
913
  - 🔄 **`reverseGradient()` helper** — flips a gradient's stop order (works with arrays or `ReusableGradient`)
860
914
  - 🎯 **`presets` exported as canonical name** — alongside the existing `colorPresets` alias
861
915
 
862
- ```ts
916
+ ```js
863
917
  import { createGradient, reverseGradient } from 'ansimax';
864
918
 
865
919
  const fire = createGradient(['#ff5555', '#ffb86c', '#f1fa8c']);
@@ -880,8 +934,8 @@ Patch release adding a performance-oriented API:
880
934
  - 📖 More JSDoc with runnable examples
881
935
  - 🎯 Matches the `ColorFn` signature — works as `colorFn` in `ascii.banner`, themes, etc.
882
936
 
883
- ```ts
884
- import { createGradient } from 'ansimax';
937
+ ```js
938
+ import { createGradient, ascii } from 'ansimax';
885
939
 
886
940
  const fire = createGradient(['#ff5555', '#ffb86c', '#f1fa8c']);
887
941
  console.log(fire('Reused colors!'));
@@ -910,7 +964,7 @@ Minor release closing the gradient engine roadmap with three powerful features:
910
964
  - 📐 **Easing curves** — `linear` / `ease-in` / `ease-out` / `ease-in-out` / `cubic-bezier` / custom functions
911
965
  - ⭕ **Conic gradients** — `gradientRect({ style: 'conic', startAngle })` for radial sweeps
912
966
 
913
- ```ts
967
+ ```js
914
968
  import { animateGradient } from 'ansimax';
915
969
 
916
970
  const ctrl = animateGradient('Loading...', ['#ff79c6', '#bd93f9', '#8be9fd']);
package/dist/index.d.mts CHANGED
@@ -1117,16 +1117,38 @@ interface StreamOptions {
1117
1117
  * Character ramps for luminance → glyph mapping.
1118
1118
  * Each ramp is ordered dark → light.
1119
1119
  *
1120
- * - `standard` — balanced 10-char ramp, works for most images
1120
+ * - `standard` — balanced 10-char ramp, works for most images (default)
1121
1121
  * - `detailed` — 70-char ramp from Paul Bourke, max detail at small sizes
1122
1122
  * - `blocks` — Unicode block fills, looks like a real photo at distance
1123
1123
  * - `simple` — 4-char minimal ramp
1124
+ * - `binary` — pure 2-char ramp: space + filled block
1125
+ * - `dots` — Unicode braille dots (sparse aesthetic)
1126
+ * - `shades` — Unicode shading gradient with high tonal range
1127
+ * - `ascii64` — printable ASCII subset, 64 chars, good for non-Unicode terminals
1128
+ *
1129
+ * @example
1130
+ * ```js
1131
+ * import { ascii, ASCII_RAMPS } from 'ansimax';
1132
+ *
1133
+ * console.log(ASCII_RAMPS.blocks); // ' ░▒▓█'
1134
+ * console.log(ASCII_RAMPS.shades); // ' ⠁⠃⠇⠧⠷⡷⣷⣿█'
1135
+ *
1136
+ * // Use directly by name
1137
+ * ascii.fromImage(pixels, { ramp: 'shades' });
1138
+ *
1139
+ * // Or pass a custom ramp string
1140
+ * ascii.fromImage(pixels, { ramp: ' .oO@' });
1141
+ * ```
1124
1142
  */
1125
1143
  declare const ASCII_RAMPS: {
1126
1144
  readonly standard: " .:-=+*#%@";
1127
1145
  readonly detailed: " .'`^\",:;Il!i><~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$";
1128
1146
  readonly blocks: " ░▒▓█";
1129
1147
  readonly simple: " .+#";
1148
+ readonly binary: " █";
1149
+ readonly dots: " ⠁⠃⠇⠧⠷⡷⣷⣿";
1150
+ readonly shades: " ⠁⠃⠇⠧⠷⡷⣷⣿█";
1151
+ readonly ascii64: " `.'^,_:-+=<>i!lI?/\\|()1{}[]rcvunxzjftLCJUYXZO0Qoahkbdpqwm*WMB8&%$#@";
1130
1152
  };
1131
1153
  type AsciiRamp = keyof typeof ASCII_RAMPS | string;
1132
1154
  /**
@@ -1199,6 +1221,32 @@ interface FromImageOptions {
1199
1221
  * midtone detail (where faces typically live). Best for portrait input.
1200
1222
  */
1201
1223
  faceMode?: boolean;
1224
+ /**
1225
+ * Render the source pixel's color as the **background** instead of the
1226
+ * foreground. Useful when paired with `ramp: 'binary'` for a photo-like
1227
+ * effect where chars become "pixels". Default `false`.
1228
+ *
1229
+ * Implies `color: true` — does not need to be set separately.
1230
+ *
1231
+ * @since 1.2.6
1232
+ */
1233
+ bgColor?: boolean;
1234
+ /**
1235
+ * Brightness adjustment applied to luminance before quantization.
1236
+ * Range `[-1, 1]`. `0` = no change, `0.2` = lighter, `-0.2` = darker.
1237
+ * Default `0`.
1238
+ *
1239
+ * @since 1.2.6
1240
+ */
1241
+ brightness?: number;
1242
+ /**
1243
+ * Contrast adjustment applied to luminance before quantization.
1244
+ * Range `[-1, 1]`. `0` = no change, `0.5` = boosted, `-0.5` = flattened.
1245
+ * Default `0`.
1246
+ *
1247
+ * @since 1.2.6
1248
+ */
1249
+ contrast?: number;
1202
1250
  }
1203
1251
  declare const fromImage: (pixels: PixelGrid, opts?: FromImageOptions) => string;
1204
1252
  /** A parsed FIGfont — opaque to the user; pass to `ascii.figlet()`. */
@@ -1244,10 +1292,24 @@ declare const parseFiglet: (flfContent: string) => FigletFont;
1244
1292
  * ```
1245
1293
  */
1246
1294
  interface FigletOptions {
1247
- /** Trim leading/trailing spaces per row. Default `true`. */
1295
+ /** Trim leading/trailing blank rows. Default `true`. */
1248
1296
  trim?: boolean;
1249
1297
  /** Color function applied to the assembled output. */
1250
1298
  colorFn?: ColorFn | null;
1299
+ /**
1300
+ * Extra spacing (in characters) inserted between each glyph.
1301
+ * `0` = touching glyphs, `1` = one-space gap, etc. Default `0`.
1302
+ *
1303
+ * @since 1.2.6
1304
+ */
1305
+ kerning?: number;
1306
+ /**
1307
+ * Vertical spacing (blank lines) between rendered lines when `text`
1308
+ * contains `\n`. Default `0`.
1309
+ *
1310
+ * @since 1.2.6
1311
+ */
1312
+ lineSpacing?: number;
1251
1313
  }
1252
1314
  declare const figletText: (text: string, font: FigletFont, opts?: FigletOptions) => string;
1253
1315
  declare const ascii: {
package/dist/index.d.ts CHANGED
@@ -1117,16 +1117,38 @@ interface StreamOptions {
1117
1117
  * Character ramps for luminance → glyph mapping.
1118
1118
  * Each ramp is ordered dark → light.
1119
1119
  *
1120
- * - `standard` — balanced 10-char ramp, works for most images
1120
+ * - `standard` — balanced 10-char ramp, works for most images (default)
1121
1121
  * - `detailed` — 70-char ramp from Paul Bourke, max detail at small sizes
1122
1122
  * - `blocks` — Unicode block fills, looks like a real photo at distance
1123
1123
  * - `simple` — 4-char minimal ramp
1124
+ * - `binary` — pure 2-char ramp: space + filled block
1125
+ * - `dots` — Unicode braille dots (sparse aesthetic)
1126
+ * - `shades` — Unicode shading gradient with high tonal range
1127
+ * - `ascii64` — printable ASCII subset, 64 chars, good for non-Unicode terminals
1128
+ *
1129
+ * @example
1130
+ * ```js
1131
+ * import { ascii, ASCII_RAMPS } from 'ansimax';
1132
+ *
1133
+ * console.log(ASCII_RAMPS.blocks); // ' ░▒▓█'
1134
+ * console.log(ASCII_RAMPS.shades); // ' ⠁⠃⠇⠧⠷⡷⣷⣿█'
1135
+ *
1136
+ * // Use directly by name
1137
+ * ascii.fromImage(pixels, { ramp: 'shades' });
1138
+ *
1139
+ * // Or pass a custom ramp string
1140
+ * ascii.fromImage(pixels, { ramp: ' .oO@' });
1141
+ * ```
1124
1142
  */
1125
1143
  declare const ASCII_RAMPS: {
1126
1144
  readonly standard: " .:-=+*#%@";
1127
1145
  readonly detailed: " .'`^\",:;Il!i><~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$";
1128
1146
  readonly blocks: " ░▒▓█";
1129
1147
  readonly simple: " .+#";
1148
+ readonly binary: " █";
1149
+ readonly dots: " ⠁⠃⠇⠧⠷⡷⣷⣿";
1150
+ readonly shades: " ⠁⠃⠇⠧⠷⡷⣷⣿█";
1151
+ readonly ascii64: " `.'^,_:-+=<>i!lI?/\\|()1{}[]rcvunxzjftLCJUYXZO0Qoahkbdpqwm*WMB8&%$#@";
1130
1152
  };
1131
1153
  type AsciiRamp = keyof typeof ASCII_RAMPS | string;
1132
1154
  /**
@@ -1199,6 +1221,32 @@ interface FromImageOptions {
1199
1221
  * midtone detail (where faces typically live). Best for portrait input.
1200
1222
  */
1201
1223
  faceMode?: boolean;
1224
+ /**
1225
+ * Render the source pixel's color as the **background** instead of the
1226
+ * foreground. Useful when paired with `ramp: 'binary'` for a photo-like
1227
+ * effect where chars become "pixels". Default `false`.
1228
+ *
1229
+ * Implies `color: true` — does not need to be set separately.
1230
+ *
1231
+ * @since 1.2.6
1232
+ */
1233
+ bgColor?: boolean;
1234
+ /**
1235
+ * Brightness adjustment applied to luminance before quantization.
1236
+ * Range `[-1, 1]`. `0` = no change, `0.2` = lighter, `-0.2` = darker.
1237
+ * Default `0`.
1238
+ *
1239
+ * @since 1.2.6
1240
+ */
1241
+ brightness?: number;
1242
+ /**
1243
+ * Contrast adjustment applied to luminance before quantization.
1244
+ * Range `[-1, 1]`. `0` = no change, `0.5` = boosted, `-0.5` = flattened.
1245
+ * Default `0`.
1246
+ *
1247
+ * @since 1.2.6
1248
+ */
1249
+ contrast?: number;
1202
1250
  }
1203
1251
  declare const fromImage: (pixels: PixelGrid, opts?: FromImageOptions) => string;
1204
1252
  /** A parsed FIGfont — opaque to the user; pass to `ascii.figlet()`. */
@@ -1244,10 +1292,24 @@ declare const parseFiglet: (flfContent: string) => FigletFont;
1244
1292
  * ```
1245
1293
  */
1246
1294
  interface FigletOptions {
1247
- /** Trim leading/trailing spaces per row. Default `true`. */
1295
+ /** Trim leading/trailing blank rows. Default `true`. */
1248
1296
  trim?: boolean;
1249
1297
  /** Color function applied to the assembled output. */
1250
1298
  colorFn?: ColorFn | null;
1299
+ /**
1300
+ * Extra spacing (in characters) inserted between each glyph.
1301
+ * `0` = touching glyphs, `1` = one-space gap, etc. Default `0`.
1302
+ *
1303
+ * @since 1.2.6
1304
+ */
1305
+ kerning?: number;
1306
+ /**
1307
+ * Vertical spacing (blank lines) between rendered lines when `text`
1308
+ * contains `\n`. Default `0`.
1309
+ *
1310
+ * @since 1.2.6
1311
+ */
1312
+ lineSpacing?: number;
1251
1313
  }
1252
1314
  declare const figletText: (text: string, font: FigletFont, opts?: FigletOptions) => string;
1253
1315
  declare const ascii: {
package/dist/index.js CHANGED
@@ -2523,7 +2523,12 @@ var ASCII_RAMPS = {
2523
2523
  standard: " .:-=+*#%@",
2524
2524
  detailed: " .'`^\",:;Il!i><~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$",
2525
2525
  blocks: " \u2591\u2592\u2593\u2588",
2526
- simple: " .+#"
2526
+ simple: " .+#",
2527
+ // v1.2.6 — new ramps
2528
+ binary: " \u2588",
2529
+ dots: " \u2801\u2803\u2807\u2827\u2837\u2877\u28F7\u28FF",
2530
+ shades: " \u2801\u2803\u2807\u2827\u2837\u2877\u28F7\u28FF\u2588",
2531
+ ascii64: " `.'^,_:-+=<>i!lI?/\\|()1{}[]rcvunxzjftLCJUYXZO0Qoahkbdpqwm*WMB8&%$#@"
2527
2532
  };
2528
2533
  var _resolveRamp = (r) => {
2529
2534
  if (typeof r === "string" && r.length > 0) {
@@ -2626,6 +2631,17 @@ var _enhanceForFace = (lum) => {
2626
2631
  })
2627
2632
  );
2628
2633
  };
2634
+ var _adjustBrightnessContrast = (lum, brightness, contrast) => {
2635
+ const safeBrightness = Math.max(-1, Math.min(1, brightness)) * 255;
2636
+ const safeContrast = Math.max(-1, Math.min(1, contrast));
2637
+ const contrastFactor = 1 + safeContrast;
2638
+ return lum.map(
2639
+ (row) => row.map((v) => {
2640
+ const adjusted = (v - 128) * contrastFactor + 128 + safeBrightness;
2641
+ return Math.max(0, Math.min(255, adjusted));
2642
+ })
2643
+ );
2644
+ };
2629
2645
  var fromImage = (pixels, opts = {}) => {
2630
2646
  if (!Array.isArray(pixels) || pixels.length === 0) return "";
2631
2647
  const firstRow = pixels[0];
@@ -2638,7 +2654,11 @@ var fromImage = (pixels, opts = {}) => {
2638
2654
  edgeDetect = "none",
2639
2655
  edgeThreshold = 40,
2640
2656
  color: color2 = false,
2641
- faceMode = false
2657
+ faceMode = false,
2658
+ // v1.2.6
2659
+ bgColor = false,
2660
+ brightness = 0,
2661
+ contrast = 0
2642
2662
  } = opts;
2643
2663
  const srcH = pixels.length;
2644
2664
  const srcW = pixels[0].length;
@@ -2647,6 +2667,9 @@ var fromImage = (pixels, opts = {}) => {
2647
2667
  const safeH = opts.height != null ? Math.max(1, Math.floor(opts.height)) : computedH;
2648
2668
  const resized = _resizePixels(pixels, safeW, safeH);
2649
2669
  let lum = _toLuminanceGrid(resized);
2670
+ if (brightness !== 0 || contrast !== 0) {
2671
+ lum = _adjustBrightnessContrast(lum, brightness, contrast);
2672
+ }
2650
2673
  if (faceMode) lum = _enhanceForFace(lum);
2651
2674
  let edgeGrid = null;
2652
2675
  if (edgeDetect === "sobel") {
@@ -2657,7 +2680,7 @@ var fromImage = (pixels, opts = {}) => {
2657
2680
  if (dither === "floyd-steinberg" && !edgeGrid) {
2658
2681
  lum = _floydSteinberg(lum, rampLen);
2659
2682
  }
2660
- const useColor = color2 && !isNoColor();
2683
+ const useColor = (color2 || bgColor) && !isNoColor();
2661
2684
  const lines = [];
2662
2685
  for (let y = 0; y < safeH; y++) {
2663
2686
  const lumRow = lum[y];
@@ -2679,7 +2702,11 @@ var fromImage = (pixels, opts = {}) => {
2679
2702
  if (useColor) {
2680
2703
  const p = pxRow[x];
2681
2704
  if (p) {
2682
- line += fgRgb(p.r, p.g, p.b) + ch;
2705
+ if (bgColor) {
2706
+ line += bgRgb(p.r, p.g, p.b) + ch;
2707
+ } else {
2708
+ line += fgRgb(p.r, p.g, p.b) + ch;
2709
+ }
2683
2710
  } else {
2684
2711
  line += ch;
2685
2712
  }
@@ -2733,7 +2760,30 @@ var parseFiglet = (flfContent) => {
2733
2760
  var figletText = (text, font, opts = {}) => {
2734
2761
  if (typeof text !== "string") return "";
2735
2762
  if (!font || !font.glyphs || font.height <= 0) return "";
2736
- const { trim = true, colorFn = null } = opts;
2763
+ const {
2764
+ trim = true,
2765
+ colorFn = null,
2766
+ // v1.2.6
2767
+ kerning = 0,
2768
+ lineSpacing = 0
2769
+ } = opts;
2770
+ const safeKerning = Math.max(0, Math.floor(kerning));
2771
+ const safeLineSpacing = Math.max(0, Math.floor(lineSpacing));
2772
+ if (text.includes("\n")) {
2773
+ const linesOfText = text.split("\n");
2774
+ const renderedBlocks = linesOfText.map(
2775
+ (line) => _renderFigletLine(line, font, safeKerning, trim)
2776
+ );
2777
+ const spacer = safeLineSpacing > 0 ? "\n".repeat(safeLineSpacing + 1) : "\n";
2778
+ let multiResult = renderedBlocks.join(spacer);
2779
+ if (colorFn) multiResult = colorFn(multiResult);
2780
+ return multiResult;
2781
+ }
2782
+ let result = _renderFigletLine(text, font, safeKerning, trim);
2783
+ if (colorFn) result = colorFn(result);
2784
+ return result;
2785
+ };
2786
+ var _renderFigletLine = (text, font, kerning, trim) => {
2737
2787
  const glyphsForText = [];
2738
2788
  for (const ch of text) {
2739
2789
  const code = ch.codePointAt(0) ?? 32;
@@ -2745,23 +2795,26 @@ var figletText = (text, font, opts = {}) => {
2745
2795
  glyphsForText.push(fallback ?? new Array(font.height).fill(""));
2746
2796
  }
2747
2797
  }
2798
+ const kerningSpacer = kerning > 0 ? " ".repeat(kerning) : "";
2748
2799
  const hardblankRe = new RegExp(_escapeRe(font.hardblank), "g");
2749
2800
  const rows = [];
2750
2801
  for (let r = 0; r < font.height; r++) {
2751
2802
  let row = "";
2752
- for (const g of glyphsForText) {
2803
+ for (let i = 0; i < glyphsForText.length; i++) {
2804
+ const g = glyphsForText[i];
2753
2805
  row += g[r] ?? "";
2806
+ if (i < glyphsForText.length - 1 && kerningSpacer) {
2807
+ row += kerningSpacer;
2808
+ }
2754
2809
  }
2755
2810
  row = row.replace(hardblankRe, " ");
2756
2811
  rows.push(row);
2757
2812
  }
2758
- let result = rows.join("\n");
2759
2813
  if (trim) {
2760
2814
  const trimmed = rows.filter((row) => row.trim().length > 0);
2761
- result = trimmed.length > 0 ? trimmed.join("\n") : rows.join("\n");
2815
+ return trimmed.length > 0 ? trimmed.join("\n") : rows.join("\n");
2762
2816
  }
2763
- if (colorFn) result = colorFn(result);
2764
- return result;
2817
+ return rows.join("\n");
2765
2818
  };
2766
2819
  var _escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2767
2820
  var ascii = {
package/dist/index.mjs CHANGED
@@ -2343,7 +2343,12 @@ var ASCII_RAMPS = {
2343
2343
  standard: " .:-=+*#%@",
2344
2344
  detailed: " .'`^\",:;Il!i><~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$",
2345
2345
  blocks: " \u2591\u2592\u2593\u2588",
2346
- simple: " .+#"
2346
+ simple: " .+#",
2347
+ // v1.2.6 — new ramps
2348
+ binary: " \u2588",
2349
+ dots: " \u2801\u2803\u2807\u2827\u2837\u2877\u28F7\u28FF",
2350
+ shades: " \u2801\u2803\u2807\u2827\u2837\u2877\u28F7\u28FF\u2588",
2351
+ ascii64: " `.'^,_:-+=<>i!lI?/\\|()1{}[]rcvunxzjftLCJUYXZO0Qoahkbdpqwm*WMB8&%$#@"
2347
2352
  };
2348
2353
  var _resolveRamp = (r) => {
2349
2354
  if (typeof r === "string" && r.length > 0) {
@@ -2446,6 +2451,17 @@ var _enhanceForFace = (lum) => {
2446
2451
  })
2447
2452
  );
2448
2453
  };
2454
+ var _adjustBrightnessContrast = (lum, brightness, contrast) => {
2455
+ const safeBrightness = Math.max(-1, Math.min(1, brightness)) * 255;
2456
+ const safeContrast = Math.max(-1, Math.min(1, contrast));
2457
+ const contrastFactor = 1 + safeContrast;
2458
+ return lum.map(
2459
+ (row) => row.map((v) => {
2460
+ const adjusted = (v - 128) * contrastFactor + 128 + safeBrightness;
2461
+ return Math.max(0, Math.min(255, adjusted));
2462
+ })
2463
+ );
2464
+ };
2449
2465
  var fromImage = (pixels, opts = {}) => {
2450
2466
  if (!Array.isArray(pixels) || pixels.length === 0) return "";
2451
2467
  const firstRow = pixels[0];
@@ -2458,7 +2474,11 @@ var fromImage = (pixels, opts = {}) => {
2458
2474
  edgeDetect = "none",
2459
2475
  edgeThreshold = 40,
2460
2476
  color: color2 = false,
2461
- faceMode = false
2477
+ faceMode = false,
2478
+ // v1.2.6
2479
+ bgColor = false,
2480
+ brightness = 0,
2481
+ contrast = 0
2462
2482
  } = opts;
2463
2483
  const srcH = pixels.length;
2464
2484
  const srcW = pixels[0].length;
@@ -2467,6 +2487,9 @@ var fromImage = (pixels, opts = {}) => {
2467
2487
  const safeH = opts.height != null ? Math.max(1, Math.floor(opts.height)) : computedH;
2468
2488
  const resized = _resizePixels(pixels, safeW, safeH);
2469
2489
  let lum = _toLuminanceGrid(resized);
2490
+ if (brightness !== 0 || contrast !== 0) {
2491
+ lum = _adjustBrightnessContrast(lum, brightness, contrast);
2492
+ }
2470
2493
  if (faceMode) lum = _enhanceForFace(lum);
2471
2494
  let edgeGrid = null;
2472
2495
  if (edgeDetect === "sobel") {
@@ -2477,7 +2500,7 @@ var fromImage = (pixels, opts = {}) => {
2477
2500
  if (dither === "floyd-steinberg" && !edgeGrid) {
2478
2501
  lum = _floydSteinberg(lum, rampLen);
2479
2502
  }
2480
- const useColor = color2 && !isNoColor();
2503
+ const useColor = (color2 || bgColor) && !isNoColor();
2481
2504
  const lines = [];
2482
2505
  for (let y = 0; y < safeH; y++) {
2483
2506
  const lumRow = lum[y];
@@ -2499,7 +2522,11 @@ var fromImage = (pixels, opts = {}) => {
2499
2522
  if (useColor) {
2500
2523
  const p = pxRow[x];
2501
2524
  if (p) {
2502
- line += fgRgb(p.r, p.g, p.b) + ch;
2525
+ if (bgColor) {
2526
+ line += bgRgb(p.r, p.g, p.b) + ch;
2527
+ } else {
2528
+ line += fgRgb(p.r, p.g, p.b) + ch;
2529
+ }
2503
2530
  } else {
2504
2531
  line += ch;
2505
2532
  }
@@ -2553,7 +2580,30 @@ var parseFiglet = (flfContent) => {
2553
2580
  var figletText = (text, font, opts = {}) => {
2554
2581
  if (typeof text !== "string") return "";
2555
2582
  if (!font || !font.glyphs || font.height <= 0) return "";
2556
- const { trim = true, colorFn = null } = opts;
2583
+ const {
2584
+ trim = true,
2585
+ colorFn = null,
2586
+ // v1.2.6
2587
+ kerning = 0,
2588
+ lineSpacing = 0
2589
+ } = opts;
2590
+ const safeKerning = Math.max(0, Math.floor(kerning));
2591
+ const safeLineSpacing = Math.max(0, Math.floor(lineSpacing));
2592
+ if (text.includes("\n")) {
2593
+ const linesOfText = text.split("\n");
2594
+ const renderedBlocks = linesOfText.map(
2595
+ (line) => _renderFigletLine(line, font, safeKerning, trim)
2596
+ );
2597
+ const spacer = safeLineSpacing > 0 ? "\n".repeat(safeLineSpacing + 1) : "\n";
2598
+ let multiResult = renderedBlocks.join(spacer);
2599
+ if (colorFn) multiResult = colorFn(multiResult);
2600
+ return multiResult;
2601
+ }
2602
+ let result = _renderFigletLine(text, font, safeKerning, trim);
2603
+ if (colorFn) result = colorFn(result);
2604
+ return result;
2605
+ };
2606
+ var _renderFigletLine = (text, font, kerning, trim) => {
2557
2607
  const glyphsForText = [];
2558
2608
  for (const ch of text) {
2559
2609
  const code = ch.codePointAt(0) ?? 32;
@@ -2565,23 +2615,26 @@ var figletText = (text, font, opts = {}) => {
2565
2615
  glyphsForText.push(fallback ?? new Array(font.height).fill(""));
2566
2616
  }
2567
2617
  }
2618
+ const kerningSpacer = kerning > 0 ? " ".repeat(kerning) : "";
2568
2619
  const hardblankRe = new RegExp(_escapeRe(font.hardblank), "g");
2569
2620
  const rows = [];
2570
2621
  for (let r = 0; r < font.height; r++) {
2571
2622
  let row = "";
2572
- for (const g of glyphsForText) {
2623
+ for (let i = 0; i < glyphsForText.length; i++) {
2624
+ const g = glyphsForText[i];
2573
2625
  row += g[r] ?? "";
2626
+ if (i < glyphsForText.length - 1 && kerningSpacer) {
2627
+ row += kerningSpacer;
2628
+ }
2574
2629
  }
2575
2630
  row = row.replace(hardblankRe, " ");
2576
2631
  rows.push(row);
2577
2632
  }
2578
- let result = rows.join("\n");
2579
2633
  if (trim) {
2580
2634
  const trimmed = rows.filter((row) => row.trim().length > 0);
2581
- result = trimmed.length > 0 ? trimmed.join("\n") : rows.join("\n");
2635
+ return trimmed.length > 0 ? trimmed.join("\n") : rows.join("\n");
2582
2636
  }
2583
- if (colorFn) result = colorFn(result);
2584
- return result;
2637
+ return rows.join("\n");
2585
2638
  };
2586
2639
  var _escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2587
2640
  var ascii = {
@@ -42,10 +42,10 @@ console.log();
42
42
 
43
43
  console.log(color.bold('━━━ Status ━━━'));
44
44
  console.log();
45
- console.log(components.status('Build started', { type: 'info' }));
46
- console.log(components.status('Linting passed', { type: 'success' }));
47
- console.log(components.status('5 deprecation warnings', { type: 'warning' }));
48
- console.log(components.status('Build failed', { type: 'error' }));
45
+ console.log(components.status('info', 'Build started'));
46
+ console.log(components.status('success', 'Linting passed'));
47
+ console.log(components.status('warn', '5 deprecation warnings'));
48
+ console.log(components.status('error', 'Build failed'));
49
49
  console.log();
50
50
 
51
51
  console.log(color.bold('━━━ Section ━━━'));
@@ -118,14 +118,14 @@ 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.5'),
121
+ components.badge('VERSION', 'v1.2.6'),
122
122
  components.badge('BUILD', 'passing'),
123
123
  components.badge('LICENSE', 'Apache 2.0'));
124
124
  console.log();
125
- console.log(components.status('Build started', { type: 'info' }));
126
- console.log(components.status('Tests passed', { type: 'success' }));
127
- console.log(components.status('1 deprecation', { type: 'warning' }));
128
- console.log(components.status('Build failed', { type: 'error' }));
125
+ console.log(components.status('info', 'Build started'));
126
+ console.log(components.status('success', 'Tests passed'));
127
+ console.log(components.status('warn', '1 deprecation'));
128
+ console.log(components.status('error', 'Build failed'));
129
129
  console.log();
130
130
 
131
131
  // ── 9. Loaders ────────────────────────────────────────────
@@ -117,14 +117,14 @@ 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.5'),
120
+ components.badge('VERSION', 'v1.2.6'),
121
121
  components.badge('BUILD', 'passing'),
122
122
  components.badge('LICENSE', 'Apache 2.0'));
123
123
  console.log();
124
- console.log(components.status('Build started', { type: 'info' }));
125
- console.log(components.status('Tests passed', { type: 'success' }));
126
- console.log(components.status('1 deprecation', { type: 'warning' }));
127
- console.log(components.status('Build failed', { type: 'error' }));
124
+ console.log(components.status('info', 'Build started'));
125
+ console.log(components.status('success', 'Tests passed'));
126
+ console.log(components.status('warn', '1 deprecation'));
127
+ console.log(components.status('error', 'Build failed'));
128
128
  console.log();
129
129
 
130
130
  // ── 9. Loaders ────────────────────────────────────────────
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ansimax",
3
- "version": "1.2.5",
3
+ "version": "1.2.6",
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",