Tinderbox Forum

Automatic generation of color table?

There is a very useful table of color names and their hex values at:

https://acrobatfaq.com/atbref8/index/VisualStyling/MapofTinderboxsdefinedco.html

Was that generated by an internal scripting expression ?

(I can see a way of doing something like that though osascript, but I wondered whether if there is a faster route expressed in terms of internal Tinderbox action script ?)

1 Like

FWIW A first sketch of a more rudimentary (osascript generated) table of color names and hex values:

basicColorSwatch.tbx (589.8 KB)

No , it’s simply a map of a container holding a note per colour/colour variant and various special colours. If you download the aTbRef source TBX you’ll see it there. Admittedly, when I created it action code was less powerful (and I knew less about action code). Still, now it’s been done, I don’t really need to re-do it.

1 Like

Thanks. The thing I had been looking for was a route to discovering (at run-time) the hex values of defined color names.

(Your aTbRef lead me to the Color.format() method and the more general format(data, formatString) function)

1 Like

Good. Always happy to hear the resource has been of use, as it makes it worth the effort of keeping it up (7 baseline versions, 14+ years and still going). :grinning:

It’s an exemplar of its genre. Makes a huge difference.

PS in case anyone is curious, in a (JS) osascript context, we can, assuming a reference to an existing note, write things like:

const strHex = (
    note.attributes.byName(
        'MyColor'
    ).value = 'bright red',
    note.evaluate({
        with: '$MyColor.format()'
    })
);
// -> #a32857
1 Like

Colophon:

To generate a swatch of the 199 basic colours named by Tinderbox 8, with the rgb values to which they map in the current display mode and color scheme, we could run the JavaScript for Automation script below, obtaining, for example, in Light mode, with the Modern color scheme, a file like this:

namedColorsLightModeModern.tbx (164.0 KB)

(() => {
    'use strict';

    // Core Tinderbox color swatch for
    // current color scheme and display mode.

    // Rob Trew 2019
    // Ver 0.4 updated to include colors '0' .. '9'
    //         and the modifier permutations of 'gray'.

    ObjC.import('AppKit');

    const main = () => {
        const inner = () => {
            const
                // Least inflected column of color names
                // (column 4 in MapofTinderboxsdefinedco.html table above)
                tbxBaseColors = [
                    'black', 'blue', 'bright blue', 'bright green',
                    'bright red', 'cool gray', 'cool gray dark', 'cyan',
                    'gray', 'green', 'magenta', 'orange', 'poppy',
                    'red', 'violet', 'warm gray', 'warm gray dark',
                    'white', 'yellow'
                ],
                tbxColorModifiers = [
                    'darkest', 'darker', 'dark',
                    '',
                    'light', 'lighter', 'lightest',
                    'muted', 'warmer', 'cooler'
                ],
                tbxAllColorNames = map(fullName)(
                    cartesianProduct(tbxColorModifiers)(
                        tbxBaseColors
                    )
                ).concat(
                    map(str)(
                        enumFromTo(1)(9)
                    )
                );

            const
                appTBX8 = Application('Tinderbox 8'),
                docs = appTBX8.documents,
                newDoc = new appTBX8.Document(),
                doc = (
                    docs.push(newDoc),
                    newDoc
                ),
                hexFromName = hexAndNoteFromColorName(appTBX8)(
                    doc.notes
                ),
                intTotal = tbxAllColorNames.length,
                strTotal = str(intTotal);

            const strListing = (
                // EFFECT
                appTBX8.activate(),
                // VALUE
                unlines(
                    zipWith(
                        i => k => {
                            const strHex = hexFromName(k);
                            return (
                                showLog(
                                    str(i) + ' of ' + strTotal,
                                    k,
                                    strHex
                                ),
                                k + '\t' + strHex
                            );
                        }
                    )(
                        enumFromTo(1)(intTotal)
                    )(tbxAllColorNames)
                )
            );

            return (
                // EFFECTS
                appTBX8.activate(),
                menuItemClicked('Tinderbox 8')(
                    ['View', 'Arrange', 'Cleanup…']
                ),
                // EFFECT AND VALUE
                copyText(strListing),
                strListing
            );
        };

        // COLORS -----------------------------------------

        // fullName :: (String, String) -> String
        const fullName = prefixNameTuple => {
            const [p, k] = Array.from(prefixNameTuple);
            return Boolean(p) ? (
                p + ' ' + k
            ) : p + k;
        };

        // hexAndNoteFromColorName ::
        //          TBX App -> TBX Notes -> String -> IO String
        const hexAndNoteFromColorName = tbx => notes => k => {
            const
                newNote = new tbx.Note({
                    name: k
                }),
                attribs = (
                    notes.push(newNote),
                    newNote.attributes
                ),
                strHex = (
                    zipWith(
                        k => v => attribs.byName(k).value = v
                    )(['Color', 'Width'])([
                        k, 6
                    ]),
                    newNote.evaluate({
                        with: '$Color.format()'
                    })
                );
            return (
                attribs.byName('Subtitle').value = strHex,
                strHex
            );
        };

        return inner();
    };

    // JXA ------------------------------------------------

    // copyText :: String -> IO Bool
    const copyText = s => {
        // String copied to general pasteboard.
        const pb = $.NSPasteboard.generalPasteboard;
        return (
            pb.clearContents,
            pb.setStringForType(
                $(s),
                $.NSPasteboardTypeString
            ),
            s
        );
    };

    // menuItemClicked :: String -> [String] -> IO Bool
    const menuItemClicked = strAppName => lstMenuPath => {
        const intMenuPath = lstMenuPath.length;
        return intMenuPath > 1 ? (() => {
            const
                appProcs = Application('System Events')
                .processes.where({
                    name: strAppName
                });
            return appProcs.length > 0 ? (
                Application(strAppName)
                .activate(),
                lstMenuPath.slice(1, -1)
                .reduce(
                    (a, x) => a.menuItems[x].menus[x],
                    appProcs[0].menuBars[0].menus.byName(lstMenuPath[0])
                )
                .menuItems[lstMenuPath[intMenuPath - 1]].click(),
                true
            ) : false;
        })() : false;
    };


    // GENERIC FUNCTIONS ----------------------------------
    // https://github.com/RobTrew/prelude-jxa

    // Tuple (,) :: a -> b -> (a, b)
    const Tuple = a => b => ({
        type: 'Tuple',
        '0': a,
        '1': b,
        length: 2
    });

    // cartesianProduct :: [a] -> [b] -> [(a, b)]
    const cartesianProduct = xs => ys =>
        xs.flatMap(x => ys.flatMap(y => Tuple(x)(y)));

    // enumFromTo :: Int -> Int -> [Int]
    const enumFromTo = m => n =>
        Array.from({
            length: 1 + n - m
        }, (_, i) => m + i);

    // Returns Infinity over objects without finite length.
    // This enables zip and zipWith to choose the shorter
    // argument when one is non-finite, like cycle, repeat etc

    // length :: [a] -> Int
    const length = xs =>
        (Array.isArray(xs) || 'string' === typeof xs) ? (
            xs.length
        ) : Infinity;

    // map :: (a -> b) -> [a] -> [b]
    const map = f => xs =>
        (Array.isArray(xs) ? (
            xs
        ) : xs.split('')).map(f);

    // showLog :: a -> IO ()
    const showLog = (...args) =>
        console.log(
            args
            .map(JSON.stringify)
            .join(' -> ')
        );

    // str :: a -> String
    const str = x => x.toString();

    // take :: Int -> [a] -> [a]
    // take :: Int -> String -> String
    const take = n => xs =>
        'GeneratorFunction' !== xs.constructor.constructor.name ? (
            xs.slice(0, n)
        ) : [].concat.apply([], Array.from({
            length: n
        }, () => {
            const x = xs.next();
            return x.done ? [] : [x.value];
        }));

    // unlines :: [String] -> String
    const unlines = xs => xs.join('\n');

    // Use of `take` and `length` here allows zipping with non-finite lists
    // i.e. generators like cycle, repeat, iterate.

    // zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
    const zipWith = f => xs => ys => {
        const
            lng = Math.min(length(xs), length(ys)),
            as = take(lng)(xs),
            bs = take(lng)(ys);
        return Array.from({
            length: lng
        }, (_, i) => f(as[i])(
            bs[i]
        ));
    };

    // MAIN ---
    return main();
})();

FWIW, it remands me that the current default set of built-in colours also includes 0 to 9 which are set to give 10 shades of grey. There is also a defined colour ‘gray’ (US spelling of the colour) … and which I see is missing from aTbRef - it goes between cyan and green in the default ordering.

1 Like

File and code now updated above to add color names ‘0’ thru ‘9’ and the variations on the theme of ‘gray’.

1 Like