ansimax 1.2.3 → 1.2.5

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.mjs CHANGED
@@ -993,10 +993,11 @@ var gradient = (text, stops, opts = {}) => {
993
993
  return _gradientAnsiAware(s, colors, easingFn, phaseN);
994
994
  };
995
995
  var createGradient = (stops, defaultOpts = {}) => {
996
- const colors = Array.isArray(stops) ? stops.map(safeHex).filter((c) => c !== null) : [];
996
+ const originalStops = Array.isArray(stops) ? [...stops] : [];
997
+ const colors = originalStops.map(safeHex).filter((c) => c !== null);
997
998
  const defaultEasingFn = resolveEasing(defaultOpts.easing);
998
999
  const defaultPreserveAnsi = defaultOpts.preserveAnsi ?? false;
999
- return (text, opts = {}) => {
1000
+ const fn = ((text, opts = {}) => {
1000
1001
  const s = coerceText(text);
1001
1002
  if (!s || isNoColor()) return s;
1002
1003
  if (colors.length === 0) return s;
@@ -1012,8 +1013,31 @@ var createGradient = (stops, defaultOpts = {}) => {
1012
1013
  return _gradientPlain(s, colors, easingFn, phaseN);
1013
1014
  }
1014
1015
  return _gradientAnsiAware(s, colors, easingFn, phaseN);
1015
- };
1016
+ });
1017
+ Object.defineProperty(fn, "stops", {
1018
+ value: Object.freeze(originalStops),
1019
+ enumerable: true,
1020
+ writable: false
1021
+ });
1022
+ Object.defineProperty(fn, "resolvedStops", {
1023
+ value: Object.freeze(colors.map((c) => Object.freeze({ ...c }))),
1024
+ enumerable: true,
1025
+ writable: false
1026
+ });
1027
+ Object.defineProperty(fn, "defaultOptions", {
1028
+ value: Object.freeze({ ...defaultOpts }),
1029
+ enumerable: true,
1030
+ writable: false
1031
+ });
1032
+ return fn;
1016
1033
  };
1034
+ function reverseGradient(input) {
1035
+ if (Array.isArray(input)) {
1036
+ return [...input].reverse();
1037
+ }
1038
+ const reversedStops = [...input.stops].reverse();
1039
+ return createGradient(reversedStops, input.defaultOptions);
1040
+ }
1017
1041
  var _gradientPlain = (text, colors, easingFn, phase) => {
1018
1042
  const chars = [...text];
1019
1043
  const visible = chars.filter((c) => c !== " ").length;
@@ -2315,6 +2339,251 @@ var stream = async function* (text, opts = {}) {
2315
2339
  yield i === lines.length - 1 ? line : line + "\n";
2316
2340
  }
2317
2341
  };
2342
+ var ASCII_RAMPS = {
2343
+ standard: " .:-=+*#%@",
2344
+ detailed: " .'`^\",:;Il!i><~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$",
2345
+ blocks: " \u2591\u2592\u2593\u2588",
2346
+ simple: " .+#"
2347
+ };
2348
+ var _resolveRamp = (r) => {
2349
+ if (typeof r === "string" && r.length > 0) {
2350
+ if (r in ASCII_RAMPS) return ASCII_RAMPS[r];
2351
+ return r;
2352
+ }
2353
+ return ASCII_RAMPS.standard;
2354
+ };
2355
+ var _luminance = (p) => {
2356
+ if (!p) return 0;
2357
+ return 0.2126 * p.r + 0.7152 * p.g + 0.0722 * p.b;
2358
+ };
2359
+ var _sobelEdges = (pixels) => {
2360
+ const h = pixels.length;
2361
+ const w = h > 0 ? pixels[0].length : 0;
2362
+ const out = Array.from({ length: h }, () => new Array(w).fill(0));
2363
+ for (let y = 1; y < h - 1; y++) {
2364
+ const rowPrev = pixels[y - 1];
2365
+ const row = pixels[y];
2366
+ const rowNext = pixels[y + 1];
2367
+ const outRow = out[y];
2368
+ for (let x = 1; x < w - 1; x++) {
2369
+ const tl = _luminance(rowPrev[x - 1]);
2370
+ const t = _luminance(rowPrev[x]);
2371
+ const tr = _luminance(rowPrev[x + 1]);
2372
+ const l = _luminance(row[x - 1]);
2373
+ const r = _luminance(row[x + 1]);
2374
+ const bl = _luminance(rowNext[x - 1]);
2375
+ const b = _luminance(rowNext[x]);
2376
+ const br = _luminance(rowNext[x + 1]);
2377
+ const gx = -tl + tr - 2 * l + 2 * r - bl + br;
2378
+ const gy = -tl - 2 * t - tr + bl + 2 * b + br;
2379
+ const mag = Math.sqrt(gx * gx + gy * gy);
2380
+ outRow[x] = Math.min(255, mag);
2381
+ }
2382
+ }
2383
+ return out;
2384
+ };
2385
+ var _floydSteinberg = (lum, levels) => {
2386
+ const h = lum.length;
2387
+ if (h === 0) return lum;
2388
+ const w = lum[0].length;
2389
+ if (w === 0) return lum;
2390
+ const out = lum.map((row) => [...row]);
2391
+ const step = 255 / Math.max(1, levels - 1);
2392
+ for (let y = 0; y < h; y++) {
2393
+ const row = out[y];
2394
+ for (let x = 0; x < w; x++) {
2395
+ const oldPixel = row[x];
2396
+ const quantLevel = Math.round(oldPixel / step);
2397
+ const newPixel = quantLevel * step;
2398
+ row[x] = newPixel;
2399
+ const err = oldPixel - newPixel;
2400
+ if (x + 1 < w) {
2401
+ row[x + 1] = row[x + 1] + err * 7 / 16;
2402
+ }
2403
+ if (y + 1 < h) {
2404
+ const next = out[y + 1];
2405
+ if (x > 0) next[x - 1] = next[x - 1] + err * 3 / 16;
2406
+ next[x] = next[x] + err * 5 / 16;
2407
+ if (x + 1 < w) next[x + 1] = next[x + 1] + err * 1 / 16;
2408
+ }
2409
+ }
2410
+ }
2411
+ return out;
2412
+ };
2413
+ var _resizePixels = (pixels, targetW, targetH) => {
2414
+ const srcH = pixels.length;
2415
+ if (srcH === 0) return [];
2416
+ const srcW = pixels[0].length;
2417
+ if (srcW === 0) return [];
2418
+ const out = [];
2419
+ for (let y = 0; y < targetH; y++) {
2420
+ const sy = Math.min(srcH - 1, Math.floor(y / targetH * srcH));
2421
+ const srcRow = pixels[sy];
2422
+ const newRow = new Array(targetW);
2423
+ for (let x = 0; x < targetW; x++) {
2424
+ const sx = Math.min(srcW - 1, Math.floor(x / targetW * srcW));
2425
+ newRow[x] = srcRow[sx];
2426
+ }
2427
+ out.push(newRow);
2428
+ }
2429
+ return out;
2430
+ };
2431
+ var _toLuminanceGrid = (pixels) => {
2432
+ return pixels.map((row) => row.map((p) => _luminance(p)));
2433
+ };
2434
+ var _enhanceForFace = (lum) => {
2435
+ const flat = [];
2436
+ for (const row of lum) for (const v of row) flat.push(v);
2437
+ if (flat.length === 0) return lum;
2438
+ flat.sort((a, b) => a - b);
2439
+ const lo = flat[Math.floor(flat.length * 0.1)];
2440
+ const hi = flat[Math.floor(flat.length * 0.9)];
2441
+ const range = Math.max(1, hi - lo);
2442
+ return lum.map(
2443
+ (row) => row.map((v) => {
2444
+ const stretched = (v - lo) / range * 255;
2445
+ return Math.max(0, Math.min(255, stretched));
2446
+ })
2447
+ );
2448
+ };
2449
+ var fromImage = (pixels, opts = {}) => {
2450
+ if (!Array.isArray(pixels) || pixels.length === 0) return "";
2451
+ const firstRow = pixels[0];
2452
+ if (!Array.isArray(firstRow) || firstRow.length === 0) return "";
2453
+ const {
2454
+ width = 80,
2455
+ ramp = "standard",
2456
+ invert = false,
2457
+ dither = "none",
2458
+ edgeDetect = "none",
2459
+ edgeThreshold = 40,
2460
+ color: color2 = false,
2461
+ faceMode = false
2462
+ } = opts;
2463
+ const srcH = pixels.length;
2464
+ const srcW = pixels[0].length;
2465
+ const safeW = Math.max(1, Math.floor(width));
2466
+ const computedH = Math.max(1, Math.round(srcH / srcW * safeW * 0.5));
2467
+ const safeH = opts.height != null ? Math.max(1, Math.floor(opts.height)) : computedH;
2468
+ const resized = _resizePixels(pixels, safeW, safeH);
2469
+ let lum = _toLuminanceGrid(resized);
2470
+ if (faceMode) lum = _enhanceForFace(lum);
2471
+ let edgeGrid = null;
2472
+ if (edgeDetect === "sobel") {
2473
+ edgeGrid = _sobelEdges(resized);
2474
+ }
2475
+ const rampStr = _resolveRamp(ramp);
2476
+ const rampLen = rampStr.length;
2477
+ if (dither === "floyd-steinberg" && !edgeGrid) {
2478
+ lum = _floydSteinberg(lum, rampLen);
2479
+ }
2480
+ const useColor = color2 && !isNoColor();
2481
+ const lines = [];
2482
+ for (let y = 0; y < safeH; y++) {
2483
+ const lumRow = lum[y];
2484
+ const pxRow = resized[y];
2485
+ const edgeRow = edgeGrid ? edgeGrid[y] : null;
2486
+ let line = "";
2487
+ for (let x = 0; x < safeW; x++) {
2488
+ let charIdx;
2489
+ if (edgeRow) {
2490
+ const edge = edgeRow[x];
2491
+ const t = edge >= edgeThreshold ? Math.min(1, edge / 255) : 0;
2492
+ charIdx = invert ? Math.round((1 - t) * (rampLen - 1)) : Math.round(t * (rampLen - 1));
2493
+ } else {
2494
+ const l = lumRow[x] / 255;
2495
+ const tNorm = invert ? 1 - l : l;
2496
+ charIdx = Math.min(rampLen - 1, Math.max(0, Math.round(tNorm * (rampLen - 1))));
2497
+ }
2498
+ const ch = rampStr[charIdx];
2499
+ if (useColor) {
2500
+ const p = pxRow[x];
2501
+ if (p) {
2502
+ line += fgRgb(p.r, p.g, p.b) + ch;
2503
+ } else {
2504
+ line += ch;
2505
+ }
2506
+ } else {
2507
+ line += ch;
2508
+ }
2509
+ }
2510
+ if (useColor) line += reset();
2511
+ lines.push(line);
2512
+ }
2513
+ return lines.join("\n");
2514
+ };
2515
+ var parseFiglet = (flfContent) => {
2516
+ if (typeof flfContent !== "string" || flfContent.length === 0) {
2517
+ throw new TypeError("parseFiglet: input must be a non-empty string");
2518
+ }
2519
+ const lines = flfContent.split(/\r?\n/);
2520
+ if (lines.length === 0) {
2521
+ throw new TypeError("parseFiglet: empty content");
2522
+ }
2523
+ const header = lines[0];
2524
+ const m = /^flf2.\s*(\S)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(\d+)/.exec(header);
2525
+ if (!m) {
2526
+ throw new TypeError('parseFiglet: invalid FIGfont header (expected "flf2a$..." line)');
2527
+ }
2528
+ const hardblank = m[1];
2529
+ const height = parseInt(m[2], 10);
2530
+ const commentLines = parseInt(m[6], 10);
2531
+ if (!Number.isFinite(height) || height <= 0) {
2532
+ throw new TypeError(`parseFiglet: invalid height ${m[2]}`);
2533
+ }
2534
+ let cursor2 = 1 + Math.max(0, commentLines);
2535
+ const glyphs = /* @__PURE__ */ new Map();
2536
+ for (let code = 32; code <= 126; code++) {
2537
+ if (cursor2 + height > lines.length) break;
2538
+ const rows = [];
2539
+ for (let r = 0; r < height; r++) {
2540
+ const raw = lines[cursor2 + r];
2541
+ const endmark = raw.charAt(raw.length - 1);
2542
+ let stripped = raw;
2543
+ while (stripped.length > 0 && stripped.charAt(stripped.length - 1) === endmark) {
2544
+ stripped = stripped.slice(0, -1);
2545
+ }
2546
+ rows.push(stripped);
2547
+ }
2548
+ glyphs.set(code, rows);
2549
+ cursor2 += height;
2550
+ }
2551
+ return { hardblank, height, glyphs };
2552
+ };
2553
+ var figletText = (text, font, opts = {}) => {
2554
+ if (typeof text !== "string") return "";
2555
+ if (!font || !font.glyphs || font.height <= 0) return "";
2556
+ const { trim = true, colorFn = null } = opts;
2557
+ const glyphsForText = [];
2558
+ for (const ch of text) {
2559
+ const code = ch.codePointAt(0) ?? 32;
2560
+ const glyph = font.glyphs.get(code);
2561
+ if (glyph) {
2562
+ glyphsForText.push(glyph);
2563
+ } else {
2564
+ const fallback = font.glyphs.get(32);
2565
+ glyphsForText.push(fallback ?? new Array(font.height).fill(""));
2566
+ }
2567
+ }
2568
+ const hardblankRe = new RegExp(_escapeRe(font.hardblank), "g");
2569
+ const rows = [];
2570
+ for (let r = 0; r < font.height; r++) {
2571
+ let row = "";
2572
+ for (const g of glyphsForText) {
2573
+ row += g[r] ?? "";
2574
+ }
2575
+ row = row.replace(hardblankRe, " ");
2576
+ rows.push(row);
2577
+ }
2578
+ let result = rows.join("\n");
2579
+ if (trim) {
2580
+ const trimmed = rows.filter((row) => row.trim().length > 0);
2581
+ result = trimmed.length > 0 ? trimmed.join("\n") : rows.join("\n");
2582
+ }
2583
+ if (colorFn) result = colorFn(result);
2584
+ return result;
2585
+ };
2586
+ var _escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2318
2587
  var ascii = {
2319
2588
  big,
2320
2589
  small,
@@ -2325,6 +2594,10 @@ var ascii = {
2325
2594
  logo,
2326
2595
  stream,
2327
2596
  measure,
2597
+ // v1.2.5 — Phase 3 closure
2598
+ fromImage,
2599
+ figletText,
2600
+ parseFiglet,
2328
2601
  // Pipeline stages — exposed for custom compositions
2329
2602
  stageRender,
2330
2603
  stageAlign,
@@ -5296,6 +5569,7 @@ var withConfig = (overrides, fn) => {
5296
5569
  var ansimax = { color, animate, ascii, loader, frames, components, trees, themes, images, configure };
5297
5570
  var index_default = ansimax;
5298
5571
  export {
5572
+ ASCII_RAMPS,
5299
5573
  BEL,
5300
5574
  BG,
5301
5575
  DEFAULTS as CONFIG_DEFAULTS,
@@ -5346,11 +5620,13 @@ export {
5346
5620
  escapeRegex,
5347
5621
  fg256,
5348
5622
  fgRgb,
5623
+ figletText,
5349
5624
  filterTree,
5350
5625
  findInTree,
5351
5626
  flipHorizontal,
5352
5627
  flipVertical,
5353
5628
  frames,
5629
+ fromImage,
5354
5630
  getConfig,
5355
5631
  getConfigValue,
5356
5632
  getRenderCacheSize,
@@ -5384,8 +5660,10 @@ export {
5384
5660
  padBoth,
5385
5661
  padEnd,
5386
5662
  padStart,
5663
+ parseFiglet,
5387
5664
  pauseListeners,
5388
5665
  presetNames,
5666
+ presets,
5389
5667
  rainbow,
5390
5668
  registerFont,
5391
5669
  registerPreset,
@@ -5402,6 +5680,7 @@ export {
5402
5680
  resetLoaderCursorCount,
5403
5681
  resetNoColor,
5404
5682
  resumeListeners,
5683
+ reverseGradient,
5405
5684
  rgbTo256,
5406
5685
  rgbToHex,
5407
5686
  rotate90,
@@ -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.3'),
121
+ components.badge('VERSION', 'v1.2.5'),
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.3'),
120
+ components.badge('VERSION', 'v1.2.5'),
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.3",
3
+ "version": "1.2.5",
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",