ansimax 1.2.4 → 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/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,304 @@ 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
+ // 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&%$#@"
2532
+ };
2533
+ var _resolveRamp = (r) => {
2534
+ if (typeof r === "string" && r.length > 0) {
2535
+ if (r in ASCII_RAMPS) return ASCII_RAMPS[r];
2536
+ return r;
2537
+ }
2538
+ return ASCII_RAMPS.standard;
2539
+ };
2540
+ var _luminance = (p) => {
2541
+ if (!p) return 0;
2542
+ return 0.2126 * p.r + 0.7152 * p.g + 0.0722 * p.b;
2543
+ };
2544
+ var _sobelEdges = (pixels) => {
2545
+ const h = pixels.length;
2546
+ const w = h > 0 ? pixels[0].length : 0;
2547
+ const out = Array.from({ length: h }, () => new Array(w).fill(0));
2548
+ for (let y = 1; y < h - 1; y++) {
2549
+ const rowPrev = pixels[y - 1];
2550
+ const row = pixels[y];
2551
+ const rowNext = pixels[y + 1];
2552
+ const outRow = out[y];
2553
+ for (let x = 1; x < w - 1; x++) {
2554
+ const tl = _luminance(rowPrev[x - 1]);
2555
+ const t = _luminance(rowPrev[x]);
2556
+ const tr = _luminance(rowPrev[x + 1]);
2557
+ const l = _luminance(row[x - 1]);
2558
+ const r = _luminance(row[x + 1]);
2559
+ const bl = _luminance(rowNext[x - 1]);
2560
+ const b = _luminance(rowNext[x]);
2561
+ const br = _luminance(rowNext[x + 1]);
2562
+ const gx = -tl + tr - 2 * l + 2 * r - bl + br;
2563
+ const gy = -tl - 2 * t - tr + bl + 2 * b + br;
2564
+ const mag = Math.sqrt(gx * gx + gy * gy);
2565
+ outRow[x] = Math.min(255, mag);
2566
+ }
2567
+ }
2568
+ return out;
2569
+ };
2570
+ var _floydSteinberg = (lum, levels) => {
2571
+ const h = lum.length;
2572
+ if (h === 0) return lum;
2573
+ const w = lum[0].length;
2574
+ if (w === 0) return lum;
2575
+ const out = lum.map((row) => [...row]);
2576
+ const step = 255 / Math.max(1, levels - 1);
2577
+ for (let y = 0; y < h; y++) {
2578
+ const row = out[y];
2579
+ for (let x = 0; x < w; x++) {
2580
+ const oldPixel = row[x];
2581
+ const quantLevel = Math.round(oldPixel / step);
2582
+ const newPixel = quantLevel * step;
2583
+ row[x] = newPixel;
2584
+ const err = oldPixel - newPixel;
2585
+ if (x + 1 < w) {
2586
+ row[x + 1] = row[x + 1] + err * 7 / 16;
2587
+ }
2588
+ if (y + 1 < h) {
2589
+ const next = out[y + 1];
2590
+ if (x > 0) next[x - 1] = next[x - 1] + err * 3 / 16;
2591
+ next[x] = next[x] + err * 5 / 16;
2592
+ if (x + 1 < w) next[x + 1] = next[x + 1] + err * 1 / 16;
2593
+ }
2594
+ }
2595
+ }
2596
+ return out;
2597
+ };
2598
+ var _resizePixels = (pixels, targetW, targetH) => {
2599
+ const srcH = pixels.length;
2600
+ if (srcH === 0) return [];
2601
+ const srcW = pixels[0].length;
2602
+ if (srcW === 0) return [];
2603
+ const out = [];
2604
+ for (let y = 0; y < targetH; y++) {
2605
+ const sy = Math.min(srcH - 1, Math.floor(y / targetH * srcH));
2606
+ const srcRow = pixels[sy];
2607
+ const newRow = new Array(targetW);
2608
+ for (let x = 0; x < targetW; x++) {
2609
+ const sx = Math.min(srcW - 1, Math.floor(x / targetW * srcW));
2610
+ newRow[x] = srcRow[sx];
2611
+ }
2612
+ out.push(newRow);
2613
+ }
2614
+ return out;
2615
+ };
2616
+ var _toLuminanceGrid = (pixels) => {
2617
+ return pixels.map((row) => row.map((p) => _luminance(p)));
2618
+ };
2619
+ var _enhanceForFace = (lum) => {
2620
+ const flat = [];
2621
+ for (const row of lum) for (const v of row) flat.push(v);
2622
+ if (flat.length === 0) return lum;
2623
+ flat.sort((a, b) => a - b);
2624
+ const lo = flat[Math.floor(flat.length * 0.1)];
2625
+ const hi = flat[Math.floor(flat.length * 0.9)];
2626
+ const range = Math.max(1, hi - lo);
2627
+ return lum.map(
2628
+ (row) => row.map((v) => {
2629
+ const stretched = (v - lo) / range * 255;
2630
+ return Math.max(0, Math.min(255, stretched));
2631
+ })
2632
+ );
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
+ };
2645
+ var fromImage = (pixels, opts = {}) => {
2646
+ if (!Array.isArray(pixels) || pixels.length === 0) return "";
2647
+ const firstRow = pixels[0];
2648
+ if (!Array.isArray(firstRow) || firstRow.length === 0) return "";
2649
+ const {
2650
+ width = 80,
2651
+ ramp = "standard",
2652
+ invert = false,
2653
+ dither = "none",
2654
+ edgeDetect = "none",
2655
+ edgeThreshold = 40,
2656
+ color: color2 = false,
2657
+ faceMode = false,
2658
+ // v1.2.6
2659
+ bgColor = false,
2660
+ brightness = 0,
2661
+ contrast = 0
2662
+ } = opts;
2663
+ const srcH = pixels.length;
2664
+ const srcW = pixels[0].length;
2665
+ const safeW = Math.max(1, Math.floor(width));
2666
+ const computedH = Math.max(1, Math.round(srcH / srcW * safeW * 0.5));
2667
+ const safeH = opts.height != null ? Math.max(1, Math.floor(opts.height)) : computedH;
2668
+ const resized = _resizePixels(pixels, safeW, safeH);
2669
+ let lum = _toLuminanceGrid(resized);
2670
+ if (brightness !== 0 || contrast !== 0) {
2671
+ lum = _adjustBrightnessContrast(lum, brightness, contrast);
2672
+ }
2673
+ if (faceMode) lum = _enhanceForFace(lum);
2674
+ let edgeGrid = null;
2675
+ if (edgeDetect === "sobel") {
2676
+ edgeGrid = _sobelEdges(resized);
2677
+ }
2678
+ const rampStr = _resolveRamp(ramp);
2679
+ const rampLen = rampStr.length;
2680
+ if (dither === "floyd-steinberg" && !edgeGrid) {
2681
+ lum = _floydSteinberg(lum, rampLen);
2682
+ }
2683
+ const useColor = (color2 || bgColor) && !isNoColor();
2684
+ const lines = [];
2685
+ for (let y = 0; y < safeH; y++) {
2686
+ const lumRow = lum[y];
2687
+ const pxRow = resized[y];
2688
+ const edgeRow = edgeGrid ? edgeGrid[y] : null;
2689
+ let line = "";
2690
+ for (let x = 0; x < safeW; x++) {
2691
+ let charIdx;
2692
+ if (edgeRow) {
2693
+ const edge = edgeRow[x];
2694
+ const t = edge >= edgeThreshold ? Math.min(1, edge / 255) : 0;
2695
+ charIdx = invert ? Math.round((1 - t) * (rampLen - 1)) : Math.round(t * (rampLen - 1));
2696
+ } else {
2697
+ const l = lumRow[x] / 255;
2698
+ const tNorm = invert ? 1 - l : l;
2699
+ charIdx = Math.min(rampLen - 1, Math.max(0, Math.round(tNorm * (rampLen - 1))));
2700
+ }
2701
+ const ch = rampStr[charIdx];
2702
+ if (useColor) {
2703
+ const p = pxRow[x];
2704
+ if (p) {
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
+ }
2710
+ } else {
2711
+ line += ch;
2712
+ }
2713
+ } else {
2714
+ line += ch;
2715
+ }
2716
+ }
2717
+ if (useColor) line += reset();
2718
+ lines.push(line);
2719
+ }
2720
+ return lines.join("\n");
2721
+ };
2722
+ var parseFiglet = (flfContent) => {
2723
+ if (typeof flfContent !== "string" || flfContent.length === 0) {
2724
+ throw new TypeError("parseFiglet: input must be a non-empty string");
2725
+ }
2726
+ const lines = flfContent.split(/\r?\n/);
2727
+ if (lines.length === 0) {
2728
+ throw new TypeError("parseFiglet: empty content");
2729
+ }
2730
+ const header = lines[0];
2731
+ const m = /^flf2.\s*(\S)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(\d+)/.exec(header);
2732
+ if (!m) {
2733
+ throw new TypeError('parseFiglet: invalid FIGfont header (expected "flf2a$..." line)');
2734
+ }
2735
+ const hardblank = m[1];
2736
+ const height = parseInt(m[2], 10);
2737
+ const commentLines = parseInt(m[6], 10);
2738
+ if (!Number.isFinite(height) || height <= 0) {
2739
+ throw new TypeError(`parseFiglet: invalid height ${m[2]}`);
2740
+ }
2741
+ let cursor2 = 1 + Math.max(0, commentLines);
2742
+ const glyphs = /* @__PURE__ */ new Map();
2743
+ for (let code = 32; code <= 126; code++) {
2744
+ if (cursor2 + height > lines.length) break;
2745
+ const rows = [];
2746
+ for (let r = 0; r < height; r++) {
2747
+ const raw = lines[cursor2 + r];
2748
+ const endmark = raw.charAt(raw.length - 1);
2749
+ let stripped = raw;
2750
+ while (stripped.length > 0 && stripped.charAt(stripped.length - 1) === endmark) {
2751
+ stripped = stripped.slice(0, -1);
2752
+ }
2753
+ rows.push(stripped);
2754
+ }
2755
+ glyphs.set(code, rows);
2756
+ cursor2 += height;
2757
+ }
2758
+ return { hardblank, height, glyphs };
2759
+ };
2760
+ var figletText = (text, font, opts = {}) => {
2761
+ if (typeof text !== "string") return "";
2762
+ if (!font || !font.glyphs || font.height <= 0) return "";
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) => {
2787
+ const glyphsForText = [];
2788
+ for (const ch of text) {
2789
+ const code = ch.codePointAt(0) ?? 32;
2790
+ const glyph = font.glyphs.get(code);
2791
+ if (glyph) {
2792
+ glyphsForText.push(glyph);
2793
+ } else {
2794
+ const fallback = font.glyphs.get(32);
2795
+ glyphsForText.push(fallback ?? new Array(font.height).fill(""));
2796
+ }
2797
+ }
2798
+ const kerningSpacer = kerning > 0 ? " ".repeat(kerning) : "";
2799
+ const hardblankRe = new RegExp(_escapeRe(font.hardblank), "g");
2800
+ const rows = [];
2801
+ for (let r = 0; r < font.height; r++) {
2802
+ let row = "";
2803
+ for (let i = 0; i < glyphsForText.length; i++) {
2804
+ const g = glyphsForText[i];
2805
+ row += g[r] ?? "";
2806
+ if (i < glyphsForText.length - 1 && kerningSpacer) {
2807
+ row += kerningSpacer;
2808
+ }
2809
+ }
2810
+ row = row.replace(hardblankRe, " ");
2811
+ rows.push(row);
2812
+ }
2813
+ if (trim) {
2814
+ const trimmed = rows.filter((row) => row.trim().length > 0);
2815
+ return trimmed.length > 0 ? trimmed.join("\n") : rows.join("\n");
2816
+ }
2817
+ return rows.join("\n");
2818
+ };
2819
+ var _escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2518
2820
  var ascii = {
2519
2821
  big,
2520
2822
  small,
@@ -2525,6 +2827,10 @@ var ascii = {
2525
2827
  logo,
2526
2828
  stream,
2527
2829
  measure,
2830
+ // v1.2.5 — Phase 3 closure
2831
+ fromImage,
2832
+ figletText,
2833
+ parseFiglet,
2528
2834
  // Pipeline stages — exposed for custom compositions
2529
2835
  stageRender,
2530
2836
  stageAlign,
@@ -5497,6 +5803,7 @@ var ansimax = { color, animate, ascii, loader, frames, components, trees, themes
5497
5803
  var index_default = ansimax;
5498
5804
  // Annotate the CommonJS export names for ESM import in node:
5499
5805
  0 && (module.exports = {
5806
+ ASCII_RAMPS,
5500
5807
  BEL,
5501
5808
  BG,
5502
5809
  CONFIG_DEFAULTS,
@@ -5546,11 +5853,13 @@ var index_default = ansimax;
5546
5853
  escapeRegex,
5547
5854
  fg256,
5548
5855
  fgRgb,
5856
+ figletText,
5549
5857
  filterTree,
5550
5858
  findInTree,
5551
5859
  flipHorizontal,
5552
5860
  flipVertical,
5553
5861
  frames,
5862
+ fromImage,
5554
5863
  getConfig,
5555
5864
  getConfigValue,
5556
5865
  getRenderCacheSize,
@@ -5584,6 +5893,7 @@ var index_default = ansimax;
5584
5893
  padBoth,
5585
5894
  padEnd,
5586
5895
  padStart,
5896
+ parseFiglet,
5587
5897
  pauseListeners,
5588
5898
  presetNames,
5589
5899
  presets,
package/dist/index.mjs CHANGED
@@ -2339,6 +2339,304 @@ 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
+ // 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&%$#@"
2352
+ };
2353
+ var _resolveRamp = (r) => {
2354
+ if (typeof r === "string" && r.length > 0) {
2355
+ if (r in ASCII_RAMPS) return ASCII_RAMPS[r];
2356
+ return r;
2357
+ }
2358
+ return ASCII_RAMPS.standard;
2359
+ };
2360
+ var _luminance = (p) => {
2361
+ if (!p) return 0;
2362
+ return 0.2126 * p.r + 0.7152 * p.g + 0.0722 * p.b;
2363
+ };
2364
+ var _sobelEdges = (pixels) => {
2365
+ const h = pixels.length;
2366
+ const w = h > 0 ? pixels[0].length : 0;
2367
+ const out = Array.from({ length: h }, () => new Array(w).fill(0));
2368
+ for (let y = 1; y < h - 1; y++) {
2369
+ const rowPrev = pixels[y - 1];
2370
+ const row = pixels[y];
2371
+ const rowNext = pixels[y + 1];
2372
+ const outRow = out[y];
2373
+ for (let x = 1; x < w - 1; x++) {
2374
+ const tl = _luminance(rowPrev[x - 1]);
2375
+ const t = _luminance(rowPrev[x]);
2376
+ const tr = _luminance(rowPrev[x + 1]);
2377
+ const l = _luminance(row[x - 1]);
2378
+ const r = _luminance(row[x + 1]);
2379
+ const bl = _luminance(rowNext[x - 1]);
2380
+ const b = _luminance(rowNext[x]);
2381
+ const br = _luminance(rowNext[x + 1]);
2382
+ const gx = -tl + tr - 2 * l + 2 * r - bl + br;
2383
+ const gy = -tl - 2 * t - tr + bl + 2 * b + br;
2384
+ const mag = Math.sqrt(gx * gx + gy * gy);
2385
+ outRow[x] = Math.min(255, mag);
2386
+ }
2387
+ }
2388
+ return out;
2389
+ };
2390
+ var _floydSteinberg = (lum, levels) => {
2391
+ const h = lum.length;
2392
+ if (h === 0) return lum;
2393
+ const w = lum[0].length;
2394
+ if (w === 0) return lum;
2395
+ const out = lum.map((row) => [...row]);
2396
+ const step = 255 / Math.max(1, levels - 1);
2397
+ for (let y = 0; y < h; y++) {
2398
+ const row = out[y];
2399
+ for (let x = 0; x < w; x++) {
2400
+ const oldPixel = row[x];
2401
+ const quantLevel = Math.round(oldPixel / step);
2402
+ const newPixel = quantLevel * step;
2403
+ row[x] = newPixel;
2404
+ const err = oldPixel - newPixel;
2405
+ if (x + 1 < w) {
2406
+ row[x + 1] = row[x + 1] + err * 7 / 16;
2407
+ }
2408
+ if (y + 1 < h) {
2409
+ const next = out[y + 1];
2410
+ if (x > 0) next[x - 1] = next[x - 1] + err * 3 / 16;
2411
+ next[x] = next[x] + err * 5 / 16;
2412
+ if (x + 1 < w) next[x + 1] = next[x + 1] + err * 1 / 16;
2413
+ }
2414
+ }
2415
+ }
2416
+ return out;
2417
+ };
2418
+ var _resizePixels = (pixels, targetW, targetH) => {
2419
+ const srcH = pixels.length;
2420
+ if (srcH === 0) return [];
2421
+ const srcW = pixels[0].length;
2422
+ if (srcW === 0) return [];
2423
+ const out = [];
2424
+ for (let y = 0; y < targetH; y++) {
2425
+ const sy = Math.min(srcH - 1, Math.floor(y / targetH * srcH));
2426
+ const srcRow = pixels[sy];
2427
+ const newRow = new Array(targetW);
2428
+ for (let x = 0; x < targetW; x++) {
2429
+ const sx = Math.min(srcW - 1, Math.floor(x / targetW * srcW));
2430
+ newRow[x] = srcRow[sx];
2431
+ }
2432
+ out.push(newRow);
2433
+ }
2434
+ return out;
2435
+ };
2436
+ var _toLuminanceGrid = (pixels) => {
2437
+ return pixels.map((row) => row.map((p) => _luminance(p)));
2438
+ };
2439
+ var _enhanceForFace = (lum) => {
2440
+ const flat = [];
2441
+ for (const row of lum) for (const v of row) flat.push(v);
2442
+ if (flat.length === 0) return lum;
2443
+ flat.sort((a, b) => a - b);
2444
+ const lo = flat[Math.floor(flat.length * 0.1)];
2445
+ const hi = flat[Math.floor(flat.length * 0.9)];
2446
+ const range = Math.max(1, hi - lo);
2447
+ return lum.map(
2448
+ (row) => row.map((v) => {
2449
+ const stretched = (v - lo) / range * 255;
2450
+ return Math.max(0, Math.min(255, stretched));
2451
+ })
2452
+ );
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
+ };
2465
+ var fromImage = (pixels, opts = {}) => {
2466
+ if (!Array.isArray(pixels) || pixels.length === 0) return "";
2467
+ const firstRow = pixels[0];
2468
+ if (!Array.isArray(firstRow) || firstRow.length === 0) return "";
2469
+ const {
2470
+ width = 80,
2471
+ ramp = "standard",
2472
+ invert = false,
2473
+ dither = "none",
2474
+ edgeDetect = "none",
2475
+ edgeThreshold = 40,
2476
+ color: color2 = false,
2477
+ faceMode = false,
2478
+ // v1.2.6
2479
+ bgColor = false,
2480
+ brightness = 0,
2481
+ contrast = 0
2482
+ } = opts;
2483
+ const srcH = pixels.length;
2484
+ const srcW = pixels[0].length;
2485
+ const safeW = Math.max(1, Math.floor(width));
2486
+ const computedH = Math.max(1, Math.round(srcH / srcW * safeW * 0.5));
2487
+ const safeH = opts.height != null ? Math.max(1, Math.floor(opts.height)) : computedH;
2488
+ const resized = _resizePixels(pixels, safeW, safeH);
2489
+ let lum = _toLuminanceGrid(resized);
2490
+ if (brightness !== 0 || contrast !== 0) {
2491
+ lum = _adjustBrightnessContrast(lum, brightness, contrast);
2492
+ }
2493
+ if (faceMode) lum = _enhanceForFace(lum);
2494
+ let edgeGrid = null;
2495
+ if (edgeDetect === "sobel") {
2496
+ edgeGrid = _sobelEdges(resized);
2497
+ }
2498
+ const rampStr = _resolveRamp(ramp);
2499
+ const rampLen = rampStr.length;
2500
+ if (dither === "floyd-steinberg" && !edgeGrid) {
2501
+ lum = _floydSteinberg(lum, rampLen);
2502
+ }
2503
+ const useColor = (color2 || bgColor) && !isNoColor();
2504
+ const lines = [];
2505
+ for (let y = 0; y < safeH; y++) {
2506
+ const lumRow = lum[y];
2507
+ const pxRow = resized[y];
2508
+ const edgeRow = edgeGrid ? edgeGrid[y] : null;
2509
+ let line = "";
2510
+ for (let x = 0; x < safeW; x++) {
2511
+ let charIdx;
2512
+ if (edgeRow) {
2513
+ const edge = edgeRow[x];
2514
+ const t = edge >= edgeThreshold ? Math.min(1, edge / 255) : 0;
2515
+ charIdx = invert ? Math.round((1 - t) * (rampLen - 1)) : Math.round(t * (rampLen - 1));
2516
+ } else {
2517
+ const l = lumRow[x] / 255;
2518
+ const tNorm = invert ? 1 - l : l;
2519
+ charIdx = Math.min(rampLen - 1, Math.max(0, Math.round(tNorm * (rampLen - 1))));
2520
+ }
2521
+ const ch = rampStr[charIdx];
2522
+ if (useColor) {
2523
+ const p = pxRow[x];
2524
+ if (p) {
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
+ }
2530
+ } else {
2531
+ line += ch;
2532
+ }
2533
+ } else {
2534
+ line += ch;
2535
+ }
2536
+ }
2537
+ if (useColor) line += reset();
2538
+ lines.push(line);
2539
+ }
2540
+ return lines.join("\n");
2541
+ };
2542
+ var parseFiglet = (flfContent) => {
2543
+ if (typeof flfContent !== "string" || flfContent.length === 0) {
2544
+ throw new TypeError("parseFiglet: input must be a non-empty string");
2545
+ }
2546
+ const lines = flfContent.split(/\r?\n/);
2547
+ if (lines.length === 0) {
2548
+ throw new TypeError("parseFiglet: empty content");
2549
+ }
2550
+ const header = lines[0];
2551
+ const m = /^flf2.\s*(\S)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(-?\d+)\s+(\d+)/.exec(header);
2552
+ if (!m) {
2553
+ throw new TypeError('parseFiglet: invalid FIGfont header (expected "flf2a$..." line)');
2554
+ }
2555
+ const hardblank = m[1];
2556
+ const height = parseInt(m[2], 10);
2557
+ const commentLines = parseInt(m[6], 10);
2558
+ if (!Number.isFinite(height) || height <= 0) {
2559
+ throw new TypeError(`parseFiglet: invalid height ${m[2]}`);
2560
+ }
2561
+ let cursor2 = 1 + Math.max(0, commentLines);
2562
+ const glyphs = /* @__PURE__ */ new Map();
2563
+ for (let code = 32; code <= 126; code++) {
2564
+ if (cursor2 + height > lines.length) break;
2565
+ const rows = [];
2566
+ for (let r = 0; r < height; r++) {
2567
+ const raw = lines[cursor2 + r];
2568
+ const endmark = raw.charAt(raw.length - 1);
2569
+ let stripped = raw;
2570
+ while (stripped.length > 0 && stripped.charAt(stripped.length - 1) === endmark) {
2571
+ stripped = stripped.slice(0, -1);
2572
+ }
2573
+ rows.push(stripped);
2574
+ }
2575
+ glyphs.set(code, rows);
2576
+ cursor2 += height;
2577
+ }
2578
+ return { hardblank, height, glyphs };
2579
+ };
2580
+ var figletText = (text, font, opts = {}) => {
2581
+ if (typeof text !== "string") return "";
2582
+ if (!font || !font.glyphs || font.height <= 0) return "";
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) => {
2607
+ const glyphsForText = [];
2608
+ for (const ch of text) {
2609
+ const code = ch.codePointAt(0) ?? 32;
2610
+ const glyph = font.glyphs.get(code);
2611
+ if (glyph) {
2612
+ glyphsForText.push(glyph);
2613
+ } else {
2614
+ const fallback = font.glyphs.get(32);
2615
+ glyphsForText.push(fallback ?? new Array(font.height).fill(""));
2616
+ }
2617
+ }
2618
+ const kerningSpacer = kerning > 0 ? " ".repeat(kerning) : "";
2619
+ const hardblankRe = new RegExp(_escapeRe(font.hardblank), "g");
2620
+ const rows = [];
2621
+ for (let r = 0; r < font.height; r++) {
2622
+ let row = "";
2623
+ for (let i = 0; i < glyphsForText.length; i++) {
2624
+ const g = glyphsForText[i];
2625
+ row += g[r] ?? "";
2626
+ if (i < glyphsForText.length - 1 && kerningSpacer) {
2627
+ row += kerningSpacer;
2628
+ }
2629
+ }
2630
+ row = row.replace(hardblankRe, " ");
2631
+ rows.push(row);
2632
+ }
2633
+ if (trim) {
2634
+ const trimmed = rows.filter((row) => row.trim().length > 0);
2635
+ return trimmed.length > 0 ? trimmed.join("\n") : rows.join("\n");
2636
+ }
2637
+ return rows.join("\n");
2638
+ };
2639
+ var _escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2342
2640
  var ascii = {
2343
2641
  big,
2344
2642
  small,
@@ -2349,6 +2647,10 @@ var ascii = {
2349
2647
  logo,
2350
2648
  stream,
2351
2649
  measure,
2650
+ // v1.2.5 — Phase 3 closure
2651
+ fromImage,
2652
+ figletText,
2653
+ parseFiglet,
2352
2654
  // Pipeline stages — exposed for custom compositions
2353
2655
  stageRender,
2354
2656
  stageAlign,
@@ -5320,6 +5622,7 @@ var withConfig = (overrides, fn) => {
5320
5622
  var ansimax = { color, animate, ascii, loader, frames, components, trees, themes, images, configure };
5321
5623
  var index_default = ansimax;
5322
5624
  export {
5625
+ ASCII_RAMPS,
5323
5626
  BEL,
5324
5627
  BG,
5325
5628
  DEFAULTS as CONFIG_DEFAULTS,
@@ -5370,11 +5673,13 @@ export {
5370
5673
  escapeRegex,
5371
5674
  fg256,
5372
5675
  fgRgb,
5676
+ figletText,
5373
5677
  filterTree,
5374
5678
  findInTree,
5375
5679
  flipHorizontal,
5376
5680
  flipVertical,
5377
5681
  frames,
5682
+ fromImage,
5378
5683
  getConfig,
5379
5684
  getConfigValue,
5380
5685
  getRenderCacheSize,
@@ -5408,6 +5713,7 @@ export {
5408
5713
  padBoth,
5409
5714
  padEnd,
5410
5715
  padStart,
5716
+ parseFiglet,
5411
5717
  pauseListeners,
5412
5718
  presetNames,
5413
5719
  presets,