X-Git-Url: http://git.squeep.com/?p=squeep-roman;a=blobdiff_plain;f=index.js;fp=index.js;h=daed9d7391aad73e9fc743a2d22a93cb3168305f;hp=0000000000000000000000000000000000000000;hb=b05ede38a9aaa33ef7e7362137638d6baf61de0d;hpb=9d71de26ede6923215251b563060d7b43b9784ee diff --git a/index.js b/index.js new file mode 100644 index 0000000..daed9d7 --- /dev/null +++ b/index.js @@ -0,0 +1,100 @@ +'use strict'; + +/** + * Integer to Unicode character. + * These pre-combined numbers are only used for direct digit mappings + * for these numbers. + * A previous implementation would compact occurrences of these multi-character + * combinations in larger numbers; however, some fonts render the multi-character + * combinations as slightly different sizes than the stand-alone characters, + * which made them render oddly. + */ +const singleMapping = { + 1: '\u2160', // Ⅰ Ⅰ + 2: '\u2161', // Ⅱ Ⅱ + 3: '\u2162', // Ⅲ Ⅲ + 4: '\u2163', // Ⅳ Ⅳ + 5: '\u2164', // Ⅴ Ⅴ + 6: '\u2165', // Ⅵ Ⅵ + 7: '\u2166', // Ⅶ Ⅶ + 8: '\u2167', // Ⅷ Ⅷ + 9: '\u2168', // Ⅸ Ⅸ + 10: '\u2169', // Ⅹ Ⅹ + 11: '\u216a', // Ⅺ Ⅺ + 12: '\u216b', // Ⅻ Ⅻ +}; + +/** + * Viable mappings from a number to the Roman representation. + */ +const mapping = { + 1: '\u2160', // Ⅰ Ⅰ + 4: '\u2160\u2164', // ⅠⅤ ⅠⅤ + 5: '\u2164', // Ⅴ Ⅴ + 9: '\u2160\u2169', // Ⅸ Ⅸ + 10: '\u2169', // Ⅹ Ⅹ + 40: '\u2169\u216c', // ⅩⅬ ⅩⅬ + 50: '\u216c', // Ⅼ Ⅼ + 90: '\u2169\u216d', // ⅩⅭ ⅩⅭ + 100: '\u216d', // Ⅽ Ⅽ + 400: '\u216d\u216e', // ⅭⅮ ⅭⅮ + 500: '\u216e', // Ⅾ Ⅾ + 900: '\u216d\u216f', // ⅭⅯ ⅭⅯ + 1000: '\u216f', // Ⅿ Ⅿ +}; + +/** + * Render all characters of #s as HTML-encoded entities. + * @param {String} s + * @returns {string} + */ +function htmlEncode(s) { + return s + .split('') + .map((c) => `&#${c.codePointAt(0)};`) + .join(''); +} + +/** + * The keys of our number->roman mappings, sorted highest-first. + * We will walk this list, rendering input-value-divided- + * by-the-key-number mapped values, modulus-ing the input-value, and + * repeating for the remaining key-numbers. + */ +const divisors = Object.keys(mapping) + .sort((a, b) => b - a) + .map(Number); + +/** + * Convert a number to its Roman representation, using Unicode characters + * or HTML entities. + * @param {Number} num + * @param {Boolean} asEntities if true, return html instead of unicode + * @returns {String} + * @throws {RangeError} + */ +function toRoman(num, asEntities = false) { + if (!num || isNaN(Number(num))) { + throw new RangeError(`cannot convert '${num}' (${typeof num})`); + } + if (num < 0) { + throw new RangeError('cannot convert negative numbers'); + } + const romanDigits = []; + if (num <= 12) { + romanDigits.push(singleMapping[num]); + } else { + divisors.forEach((d) => { + const length = Math.floor(num / d); + romanDigits.push(mapping[d].repeat(length)); + num %= d; + }); + } + const romanString = romanDigits.join(''); + + return asEntities ? htmlEncode(romanString) : romanString; +} + +module.exports = { + toRoman, +}; \ No newline at end of file