// Random Font Per Character — Adobe Illustrator compatible
//
// WHAT IT DOES: gives every single character of the SELECTED text a DIFFERENT, randomly
// picked font. The classic "ransom note" / mixed-typeface headline: select a text object,
// run the script, and each glyph gets its own face drawn from the fonts installed on this
// machine (`app.textFonts` — the same list the Character palette offers).
//
// USE: select one or more text objects (a group holding text works too), run the script.
// The whole run is ONE undo step, so a single ⌘Z brings the original typeface back.
//
// HOW MANY FACES: with 0 every character draws from ALL installed fonts, so a long line
// can hold as many faces as it has letters. A small number (3, 5 …) first picks that many
// faces at random and then shuffles ONLY those across the characters — the look stays
// deliberately mixed instead of pure noise.
//
// THE VALUE IS A DECLARED PARAMETER (SK-1623): the `@param` line below tells the app the
// script takes one input, so running it — from the Scripts palette or File ▸ Scripts —
// brings up a modal with that field pre-filled, and Cancel means the script does not run.
// The declaration is a plain comment and the read is typeof-guarded, so the same .jsx
// still runs unchanged in Adobe Illustrator (and in any headless run here, e.g. the
// AppleScript `do script`), simply falling back to FONT_COUNT.
//
// @param fonts | Number of fonts (0 = all) | integer | 0 | 0..999
//
// WHITESPACE is left alone on purpose: a face on a blank glyph changes nothing you can see
// and would only add a run to the document.
//
// Per-character faces are written through textRange.characters (SVGE-1315), i.e. the same
// inline run <tspan>s the app's own text editor and the Character palette use, so the
// result survives saving, reopening and a later in-place edit.
#target illustrator

var FONT_COUNT = 0;   // 0 = draw from every installed face; N = pick N faces and mix those

var doc = app.activeDocument;

// The size of the pool, from the parameters modal (see the @param line in the header);
// headless / Adobe Illustrator ⇒ FONT_COUNT.
var wanted = (typeof SVGE_PARAMS !== 'undefined' && SVGE_PARAMS && SVGE_PARAMS.fonts != null)
           ? parseInt(SVGE_PARAMS.fonts, 10)
           : FONT_COUNT;
if (isNaN(wanted) || wanted < 0) wanted = FONT_COUNT;

// ---- the text objects to work on -------------------------------------------------------
// Snapshot the selection ONCE — doc.selection is a live getter.
var sel = doc.selection;
var frames = [];
for (var i = 0; sel && i < sel.length; i++) {
    var it = sel[i];
    if (!it) continue;
    if (it.typename === 'TextFrame') {
        frames.push(it);
    } else if (it.typename === 'GroupItem') {
        // A selected group: every text inside it counts as selected too.
        var inner = it.textFrames;
        for (var g = 0; inner && g < inner.length; g++) frames.push(inner[g]);
    }
}

if (!frames.length)
    throw new Error('Random Font Per Character needs a text selection: select one or more ' +
                    'text objects, then run the script again.');

// ---- the font pool ---------------------------------------------------------------------
var installed = app.textFonts;
var pool = [];
for (var f = 0; f < installed.length; f++) pool.push(installed[f]);

if (!pool.length)
    throw new Error('No font is available to this document, so there is nothing to pick ' +
                    'from. Install at least one font and run the script again.');

// A smaller pool: take `wanted` DISTINCT faces at random out of the installed ones.
if (wanted > 0 && wanted < pool.length) {
    var picked = [];
    while (picked.length < wanted) {
        var cand = pool[Math.floor(Math.random() * pool.length)];
        var dup = false;
        for (var p = 0; p < picked.length; p++) if (picked[p].name === cand.name) { dup = true; break; }
        if (!dup) picked.push(cand);
    }
    pool = picked;
}

// ---- assign one random face per character ----------------------------------------------
var styled = 0, skipped = 0;
for (var t = 0; t < frames.length; t++) {
    var chars = frames[t].characters;
    var n = chars.length;
    for (var c = 0; c < n; c++) {
        var ch = chars[c];
        var glyph = ch.contents;
        // Blanks keep whatever face they had (see the note in the header).
        if (!glyph || /^\s$/.test(glyph)) { skipped++; continue; }
        ch.characterAttributes.textFont = pool[Math.floor(Math.random() * pool.length)];
        styled++;
    }
}

app.redraw();
$.writeln('Random Font Per Character: ' + styled + ' character(s) restyled in ' +
          frames.length + ' text object(s) from a pool of ' + pool.length +
          ' font(s)' + (skipped ? (', ' + skipped + ' blank(s) left untouched') : '') + '.');
