ansimax 1.2.4 → 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.js CHANGED
@@ -30,6 +30,7 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
+ ASCII_RAMPS: () => ASCII_RAMPS,
33
34
  BEL: () => BEL,
34
35
  BG: () => BG,
35
36
  CONFIG_DEFAULTS: () => DEFAULTS,
@@ -80,11 +81,13 @@ __export(index_exports, {
80
81
  escapeRegex: () => escapeRegex,
81
82
  fg256: () => fg256,
82
83
  fgRgb: () => fgRgb,
84
+ figletText: () => figletText,
83
85
  filterTree: () => filterTree,
84
86
  findInTree: () => findInTree,
85
87
  flipHorizontal: () => flipHorizontal,
86
88
  flipVertical: () => flipVertical,
87
89
  frames: () => frames,
90
+ fromImage: () => fromImage,
88
91
  getConfig: () => getConfig,
89
92
  getConfigValue: () => getConfigValue,
90
93
  getRenderCacheSize: () => getRenderCacheSize,
@@ -118,6 +121,7 @@ __export(index_exports, {
118
121
  padBoth: () => padBoth,
119
122
  padEnd: () => padEnd,
120
123
  padStart: () => padStart,
124
+ parseFiglet: () => parseFiglet,
121
125
  pauseListeners: () => pauseListeners,
122
126
  presetNames: () => presetNames,
123
127
  presets: () => presets,
@@ -2515,6 +2519,251 @@ var stream = async function* (text, opts = {}) {
2515
2519
  yield i === lines.length - 1 ? line : line + "\n";
2516
2520
  }
2517
2521
  };
2522
+ var ASCII_RAMPS = {
2523
+ standard: " .:-=+*#%@",
2524
+ detailed: " .'`^\",:;Il!i><~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$",
2525
+ blocks: " \u2591\u2592\u2593\u2588",
2526
+ simple: " .+#"
2527
+ };
2528
+ var _resolveRamp = (r) => {
2529
+ if (typeof r === "string" && r.length > 0) {
2530
+ if (r in ASCII_RAMPS) return ASCII_RAMPS[r];
2531
+ return r;
2532
+ }
2533
+ return ASCII_RAMPS.standard;
2534
+ };
2535
+ var _luminance = (p) => {
2536
+ if (!p) return 0;
2537
+ return 0.2126 * p.r + 0.7152 * p.g + 0.0722 * p.b;
2538
+ };
2539
+ var _sobelEdges = (pixels) => {
2540
+ const h = pixels.length;
2541
+ const w = h > 0 ? pixels[0].length : 0;
2542
+ const out = Array.from({ length: h }, () => new Array(w).fill(0));
2543
+ for (let y = 1; y < h - 1; y++) {
2544
+ const rowPrev = pixels[y - 1];
2545
+ const row = pixels[y];
2546
+ const rowNext = pixels[y + 1];
2547
+ const outRow = out[y];
2548
+ for (let x = 1; x < w - 1; x++) {
2549
+ const tl = _luminance(rowPrev[x - 1]);
2550
+ const t = _luminance(rowPrev[x]);
2551
+ const tr = _luminance(rowPrev[x + 1]);
2552
+ const l = _luminance(row[x - 1]);
2553
+ const r = _luminance(row[x + 1]);
2554
+ const bl = _luminance(rowNext[x - 1]);
2555
+ const b = _luminance(rowNext[x]);
2556
+ const br = _luminance(rowNext[x + 1]);
2557
+ const gx = -tl + tr - 2 * l + 2 * r - bl + br;
2558
+ const gy = -tl - 2 * t - tr + bl + 2 * b + br;
2559
+ const mag = Math.sqrt(gx * gx + gy * gy);
2560
+ outRow[x] = Math.min(255, mag);
2561
+ }
2562
+ }
2563
+ return out;
2564
+ };
2565
+ var _floydSteinberg = (lum, levels) => {
2566
+ const h = lum.length;
2567
+ if (h === 0) return lum;
2568
+ const w = lum[0].length;
2569
+ if (w === 0) return lum;
2570
+ const out = lum.map((row) => [...row]);
2571
+ const step = 255 / Math.max(1, levels - 1);
2572
+ for (let y = 0; y < h; y++) {
2573
+ const row = out[y];
2574
+ for (let x = 0; x < w; x++) {
2575
+ const oldPixel = row[x];
2576
+ const quantLevel = Math.round(oldPixel / step);
2577
+ const newPixel = quantLevel * step;
2578
+ row[x] = newPixel;
2579
+ const err = oldPixel - newPixel;
2580
+ if (x + 1 < w) {
2581
+ row[x + 1] = row[x + 1] + err * 7 / 16;
2582
+ }
2583
+ if (y + 1 < h) {
2584
+ const next = out[y + 1];
2585
+ if (x > 0) next[x - 1] = next[x - 1] + err * 3 / 16;
2586
+ next[x] = next[x] + err * 5 / 16;
2587
+ if (x + 1 < w) next[x + 1] = next[x + 1] + err * 1 / 16;
2588
+ }
2589
+ }
2590
+ }
2591
+ return out;
2592
+ };
2593
+ var _resizePixels = (pixels, targetW, targetH) => {
2594
+ const srcH = pixels.length;
2595
+ if (srcH === 0) return [];
2596
+ const srcW = pixels[0].length;
2597
+ if (srcW === 0) return [];
2598
+ const out = [];
2599
+ for (let y = 0; y < targetH; y++) {
2600
+ const sy = Math.min(srcH - 1, Math.floor(y / targetH * srcH));
2601
+ const srcRow = pixels[sy];
2602
+ const newRow = new Array(targetW);
2603
+ for (let x = 0; x < targetW; x++) {
2604
+ const sx = Math.min(srcW - 1, Math.floor(x / targetW * srcW));
2605
+ newRow[x] = srcRow[sx];
2606
+ }
2607
+ out.push(newRow);
2608
+ }
2609
+ return out;
2610
+ };
2611
+ var _toLuminanceGrid = (pixels) => {
2612
+ return pixels.map((row) => row.map((p) => _luminance(p)));
2613
+ };
2614
+ var _enhanceForFace = (lum) => {
2615
+ const flat = [];
2616
+ for (const row of lum) for (const v of row) flat.push(v);
2617
+ if (flat.length === 0) return lum;
2618
+ flat.sort((a, b) => a - b);
2619
+ const lo = flat[Math.floor(flat.length * 0.1)];
2620
+ const hi = flat[Math.floor(flat.length * 0.9)];
2621
+ const range = Math.max(1, hi - lo);
2622
+ return lum.map(
2623
+ (row) => row.map((v) => {
2624
+ const stretched = (v - lo) / range * 255;
2625
+ return Math.max(0, Math.min(255, stretched));
2626
+ })
2627
+ );
2628
+ };
2629
+ var fromImage = (pixels, opts = {}) => {
2630
+ if (!Array.isArray(pixels) || pixels.length === 0) return "";
2631
+ const firstRow = pixels[0];
2632
+ if (!Array.isArray(firstRow) || firstRow.length === 0) return "";
2633
+ const {
2634
+ width = 80,
2635
+ ramp = "standard",
2636
+ invert = false,
2637
+ dither = "none",
2638
+ edgeDetect = "none",
2639
+ edgeThreshold = 40,
2640
+ color: color2 = false,
2641
+ faceMode = false
2642
+ } = opts;
2643
+ const srcH = pixels.length;
2644
+ const srcW = pixels[0].length;
2645
+ const safeW = Math.max(1, Math.floor(width));
2646
+ const computedH = Math.max(1, Math.round(srcH / srcW * safeW * 0.5));
2647
+ const safeH = opts.height != null ? Math.max(1, Math.floor(opts.height)) : computedH;
2648
+ const resized = _resizePixels(pixels, safeW, safeH);
2649
+ let lum = _toLuminanceGrid(resized);
2650
+ if (faceMode) lum = _enhanceForFace(lum);
2651
+ let edgeGrid = null;
2652
+ if (edgeDetect === "sobel") {
2653
+ edgeGrid = _sobelEdges(resized);
2654
+ }
2655
+ const rampStr = _resolveRamp(ramp);
2656
+ const rampLen = rampStr.length;
2657
+ if (dither === "floyd-steinberg" && !edgeGrid) {
2658
+ lum = _floydSteinberg(lum, rampLen);
2659
+ }
2660
+ const useColor = color2 && !isNoColor();
2661
+ const lines = [];
2662
+ for (let y = 0; y < safeH; y++) {
2663
+ const lumRow = lum[y];
2664
+ const pxRow = resized[y];
2665
+ const edgeRow = edgeGrid ? edgeGrid[y] : null;
2666
+ let line = "";
2667
+ for (let x = 0; x < safeW; x++) {
2668
+ let charIdx;
2669
+ if (edgeRow) {
2670
+ const edge = edgeRow[x];
2671
+ const t = edge >= edgeThreshold ? Math.min(1, edge / 255) : 0;
2672
+ charIdx = invert ? Math.round((1 - t) * (rampLen - 1)) : Math.round(t * (rampLen - 1));
2673
+ } else {
2674
+ const l = lumRow[x] / 255;
2675
+ const tNorm = invert ? 1 - l : l;
2676
+ charIdx = Math.min(rampLen - 1, Math.max(0, Math.round(tNorm * (rampLen - 1))));
2677
+ }
2678
+ const ch = rampStr[charIdx];
2679
+ if (useColor) {
2680
+ const p = pxRow[x];
2681
+ if (p) {
2682
+ line += fgRgb(p.r, p.g, p.b) + ch;
2683
+ } else {
2684
+ line += ch;
2685
+ }
2686
+ } else {
2687
+ line += ch;
2688
+ }
2689
+ }
2690
+ if (useColor) line += reset();
2691
+ lines.push(line);
2692
+ }
2693
+ return lines.join("\n");
2694
+ };
2695
+ var parseFiglet = (flfContent) => {
2696
+ if (typeof flfContent !== "string" || flfContent.length === 0) {
2697
+ throw new TypeError("parseFiglet: input must be a non-empty string");
2698
+ }
2699
+ const lines = flfContent.split(/\r?\n/);
2700
+ if (lines.length === 0) {
2701
+ throw new TypeError("parseFiglet: empty content");
2702
+ }
2703
+ const header = lines[0];
2704
+ const m = /^flf2.\s*(\S)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(\d+)/.exec(header);
2705
+ if (!m) {
2706
+ throw new TypeError('parseFiglet: invalid FIGfont header (expected "flf2a$..." line)');
2707
+ }
2708
+ const hardblank = m[1];
2709
+ const height = parseInt(m[2], 10);
2710
+ const commentLines = parseInt(m[6], 10);
2711
+ if (!Number.isFinite(height) || height <= 0) {
2712
+ throw new TypeError(`parseFiglet: invalid height ${m[2]}`);
2713
+ }
2714
+ let cursor2 = 1 + Math.max(0, commentLines);
2715
+ const glyphs = /* @__PURE__ */ new Map();
2716
+ for (let code = 32; code <= 126; code++) {
2717
+ if (cursor2 + height > lines.length) break;
2718
+ const rows = [];
2719
+ for (let r = 0; r < height; r++) {
2720
+ const raw = lines[cursor2 + r];
2721
+ const endmark = raw.charAt(raw.length - 1);
2722
+ let stripped = raw;
2723
+ while (stripped.length > 0 && stripped.charAt(stripped.length - 1) === endmark) {
2724
+ stripped = stripped.slice(0, -1);
2725
+ }
2726
+ rows.push(stripped);
2727
+ }
2728
+ glyphs.set(code, rows);
2729
+ cursor2 += height;
2730
+ }
2731
+ return { hardblank, height, glyphs };
2732
+ };
2733
+ var figletText = (text, font, opts = {}) => {
2734
+ if (typeof text !== "string") return "";
2735
+ if (!font || !font.glyphs || font.height <= 0) return "";
2736
+ const { trim = true, colorFn = null } = opts;
2737
+ const glyphsForText = [];
2738
+ for (const ch of text) {
2739
+ const code = ch.codePointAt(0) ?? 32;
2740
+ const glyph = font.glyphs.get(code);
2741
+ if (glyph) {
2742
+ glyphsForText.push(glyph);
2743
+ } else {
2744
+ const fallback = font.glyphs.get(32);
2745
+ glyphsForText.push(fallback ?? new Array(font.height).fill(""));
2746
+ }
2747
+ }
2748
+ const hardblankRe = new RegExp(_escapeRe(font.hardblank), "g");
2749
+ const rows = [];
2750
+ for (let r = 0; r < font.height; r++) {
2751
+ let row = "";
2752
+ for (const g of glyphsForText) {
2753
+ row += g[r] ?? "";
2754
+ }
2755
+ row = row.replace(hardblankRe, " ");
2756
+ rows.push(row);
2757
+ }
2758
+ let result = rows.join("\n");
2759
+ if (trim) {
2760
+ const trimmed = rows.filter((row) => row.trim().length > 0);
2761
+ result = trimmed.length > 0 ? trimmed.join("\n") : rows.join("\n");
2762
+ }
2763
+ if (colorFn) result = colorFn(result);
2764
+ return result;
2765
+ };
2766
+ var _escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2518
2767
  var ascii = {
2519
2768
  big,
2520
2769
  small,
@@ -2525,6 +2774,10 @@ var ascii = {
2525
2774
  logo,
2526
2775
  stream,
2527
2776
  measure,
2777
+ // v1.2.5 — Phase 3 closure
2778
+ fromImage,
2779
+ figletText,
2780
+ parseFiglet,
2528
2781
  // Pipeline stages — exposed for custom compositions
2529
2782
  stageRender,
2530
2783
  stageAlign,
@@ -5497,6 +5750,7 @@ var ansimax = { color, animate, ascii, loader, frames, components, trees, themes
5497
5750
  var index_default = ansimax;
5498
5751
  // Annotate the CommonJS export names for ESM import in node:
5499
5752
  0 && (module.exports = {
5753
+ ASCII_RAMPS,
5500
5754
  BEL,
5501
5755
  BG,
5502
5756
  CONFIG_DEFAULTS,
@@ -5546,11 +5800,13 @@ var index_default = ansimax;
5546
5800
  escapeRegex,
5547
5801
  fg256,
5548
5802
  fgRgb,
5803
+ figletText,
5549
5804
  filterTree,
5550
5805
  findInTree,
5551
5806
  flipHorizontal,
5552
5807
  flipVertical,
5553
5808
  frames,
5809
+ fromImage,
5554
5810
  getConfig,
5555
5811
  getConfigValue,
5556
5812
  getRenderCacheSize,
@@ -5584,6 +5840,7 @@ var index_default = ansimax;
5584
5840
  padBoth,
5585
5841
  padEnd,
5586
5842
  padStart,
5843
+ parseFiglet,
5587
5844
  pauseListeners,
5588
5845
  presetNames,
5589
5846
  presets,
package/dist/index.mjs CHANGED
@@ -2339,6 +2339,251 @@ var stream = async function* (text, opts = {}) {
2339
2339
  yield i === lines.length - 1 ? line : line + "\n";
2340
2340
  }
2341
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, "\\$&");
2342
2587
  var ascii = {
2343
2588
  big,
2344
2589
  small,
@@ -2349,6 +2594,10 @@ var ascii = {
2349
2594
  logo,
2350
2595
  stream,
2351
2596
  measure,
2597
+ // v1.2.5 — Phase 3 closure
2598
+ fromImage,
2599
+ figletText,
2600
+ parseFiglet,
2352
2601
  // Pipeline stages — exposed for custom compositions
2353
2602
  stageRender,
2354
2603
  stageAlign,
@@ -5320,6 +5569,7 @@ var withConfig = (overrides, fn) => {
5320
5569
  var ansimax = { color, animate, ascii, loader, frames, components, trees, themes, images, configure };
5321
5570
  var index_default = ansimax;
5322
5571
  export {
5572
+ ASCII_RAMPS,
5323
5573
  BEL,
5324
5574
  BG,
5325
5575
  DEFAULTS as CONFIG_DEFAULTS,
@@ -5370,11 +5620,13 @@ export {
5370
5620
  escapeRegex,
5371
5621
  fg256,
5372
5622
  fgRgb,
5623
+ figletText,
5373
5624
  filterTree,
5374
5625
  findInTree,
5375
5626
  flipHorizontal,
5376
5627
  flipVertical,
5377
5628
  frames,
5629
+ fromImage,
5378
5630
  getConfig,
5379
5631
  getConfigValue,
5380
5632
  getRenderCacheSize,
@@ -5408,6 +5660,7 @@ export {
5408
5660
  padBoth,
5409
5661
  padEnd,
5410
5662
  padStart,
5663
+ parseFiglet,
5411
5664
  pauseListeners,
5412
5665
  presetNames,
5413
5666
  presets,
@@ -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.4'),
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.4'),
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.4",
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",