UNPKG

mathup

Version:

Easy MathML authoring tool with a quick to write syntax

377 lines (327 loc) 7.91 kB
// See tables at https://en.wikipedia.org/wiki/Mathematical_Alphanumeric_Symbols /** * @param {(char: string) => string} fn * @param {string} string * @returns {string} */ function mapChars(fn, string) { let mapped = ""; for (const char of string) { mapped += fn(char); } return mapped; } /** * @param {[[number, number], number][]} ranges * @param {Map<string, string>} [defaults] * @returns {(char: string) => string} */ function createCharMapping(ranges, defaults) { return (char) => { if (defaults) { const found = defaults.get(char); if (found) { return found; } } const codePoint = char.codePointAt(0); if (!codePoint) { return char; } for (const [[min, max], offset] of ranges) { if (min <= codePoint && codePoint <= max) { return String.fromCodePoint(codePoint + offset); } } return char; }; } /** * @param {[number, number]} range * @returns {(offset: number) => [[number, number], number]} */ function createRange([min, max]) { return (offset) => [[min, max], offset - min]; } const digitRange = createRange([0x30, 0x39]); const latinCapitalLetterRange = createRange([0x41, 0x5a]); const latinSmallLetterRange = createRange([0x61, 0x7a]); const greekCapitalLetterRange = createRange([0x0391, 0x03a9]); const greekSmallLetterRange = createRange([0x03b1, 0x03c9]); const bold = createCharMapping( [ digitRange(0x1d7ce), latinCapitalLetterRange(0x1d400), latinSmallLetterRange(0x1d41a), greekCapitalLetterRange(0x1d6a8), greekSmallLetterRange(0x1d6c2), ], new Map([ ["ϴ", "𝚹"], ["∇", "𝛁"], ["∂", "𝛛"], ["ϵ", "𝛜"], ["ϑ", "𝛝"], ["ϰ", "𝛞"], ["ϕ", "𝛟"], ["ϱ", "𝛠"], ["ϖ", "𝛡"], ["Ϝ", "𝟊"], ["ϝ", "𝟋"], ]), ); const italic = createCharMapping( [ latinCapitalLetterRange(0x1d434), latinSmallLetterRange(0x1d44e), greekCapitalLetterRange(0x1d6e2), greekSmallLetterRange(0x1d6fc), ], new Map([ ["h", "ℎ"], ["ı", "𝚤"], ["ȷ", "𝚥"], ["ϴ", "𝛳"], ["∇", "𝛻"], ["∂", "𝜕"], ["ϵ", "𝜖 "], ["ϑ", "𝜗"], ["ϰ", "𝜘"], ["ϕ", "𝜙"], ["ϱ", "𝜚"], ["ϖ", "𝜛"], ]), ); const boldItalic = createCharMapping( [ latinCapitalLetterRange(0x1d468), latinSmallLetterRange(0x1d482), greekCapitalLetterRange(0x1d71c), greekSmallLetterRange(0x1d736), ], new Map([ ["ϴ", "𝜭"], ["∇", "𝜵"], ["∂", "𝝏"], ["ϵ", "𝝐"], ["ϑ", "𝝑"], ["ϰ", "𝝒"], ["ϕ", "𝝓"], ["ϱ", "𝝔"], ["ϖ", "𝝕"], ]), ); const doubleStruck = createCharMapping( [ digitRange(0x1d7d8), latinCapitalLetterRange(0x1d538), latinSmallLetterRange(0x1d552), ], new Map([ ["C", "ℂ"], ["H", "ℍ"], ["N", "ℕ"], ["P", "ℙ"], ["Q", "ℚ"], ["R", "ℝ"], ["Z", "ℤ"], ["π", "ℼ"], ["γ", "ℽ"], ["Π", "ℿ"], ["∑", "⅀"], ]), ); const italicDoubleStruck = createCharMapping( [], new Map([ ["D", "ⅅ"], ["d", "ⅆ"], ["i", "ⅈ"], ["j", "ⅉ"], ]), ); const fraktur = createCharMapping( [latinCapitalLetterRange(0x1d504), latinSmallLetterRange(0x1d51e)], new Map([ ["C", "ℭ"], ["H", "ℌ"], ["I", "ℑ"], ["R", "ℜ"], ["Z", "ℨ"], ]), ); const boldFraktur = createCharMapping([ latinCapitalLetterRange(0x1d56c), latinSmallLetterRange(0x1d586), ]); const monospace = createCharMapping([ digitRange(0x1d7f6), latinCapitalLetterRange(0x1d670), latinSmallLetterRange(0x1d68a), ]); const sansSerif = createCharMapping([ digitRange(0x1d7e2), latinCapitalLetterRange(0x1d5a0), latinSmallLetterRange(0x1d5ba), ]); const boldSansSerif = createCharMapping( [ digitRange(0x1d7ec), latinCapitalLetterRange(0x1d5d4), latinSmallLetterRange(0x1d5ee), greekCapitalLetterRange(0x1d756), greekSmallLetterRange(0x1d770), ], new Map([ ["ϴ", "𝝧"], ["∇", "𝝯"], ["∂", "𝞉"], ["ϵ", "𝞊"], ["ϑ", "𝞋"], ["ϰ", "𝞌"], ["ϕ", "𝞍"], ["ϱ", "𝞎"], ["ϖ", "𝞏"], ]), ); const italicSansSerif = createCharMapping([ latinCapitalLetterRange(0x1d608), latinSmallLetterRange(0x1d622), ]); const boldItalicSansSerif = createCharMapping( [ latinCapitalLetterRange(0x1d63c), latinSmallLetterRange(0x1d656), greekCapitalLetterRange(0x1d790), greekSmallLetterRange(0x1d7aa), ], new Map([ ["ϴ", "𝞡"], ["∇", "𝞩"], ["∂", "𝟃"], ["ϵ", "𝟄"], ["ϑ", "𝟅"], ["ϰ", "𝟆"], ["ϕ", "𝟇"], ["ϱ", "𝟈"], ["ϖ", "𝟉"], ]), ); const script = createCharMapping( [latinCapitalLetterRange(0x1d49c), latinSmallLetterRange(0x1d4b6)], new Map([ ["B", "ℬ"], ["E", "ℰ"], ["F", "ℱ"], ["H", "ℋ"], ["I", "ℐ"], ["L", "ℒ"], ["M", "ℳ"], ["R", "ℛ"], ["e", "ℯ"], ["g", "ℊ"], ["o", "ℴ"], ]), ); const boldScript = createCharMapping([ latinCapitalLetterRange(0x1d4d0), latinSmallLetterRange(0x1d4ea), ]); /** * @param {string} family * @returns {string} */ function fontFamilyStyleValue(family) { return `var(--mathup-font-family-${family}, ${family})`; } /** * @typedef {import("../../parser/index.js").LiteralAttrs} LiteralAttrs * @param {LiteralAttrs} attrs * @param {string[]} transforms * @returns {LiteralAttrs} */ export function textLiteralAttrs(attrs, transforms) { /** @type {Map<string, string>} */ const styles = new Map(); if (transforms.includes("bold")) { styles.set("font-weight", "bold"); } if (transforms.includes("italic")) { styles.set("font-style", "italic"); } // serif is default font-family, so no need to check for `rm`. if (transforms.includes("sans-serif")) { styles.set("font-family", fontFamilyStyleValue("sans-serif")); } else if (transforms.includes("monospace")) { styles.set("font-family", fontFamilyStyleValue("monospace")); } if (styles.size === 0) { return attrs; } let style = ""; for (const [name, value] of styles) { style += `${name}:${value};`; } return { ...attrs, style, }; } /** * @param {string} text * @param {string[]} transforms * @returns {string} */ export function textLiteralValue(text, transforms) { if (transforms.includes("double-struck")) { return mapChars(doubleStruck, text); } if (transforms.includes("script")) { return mapChars(script, text); } if (transforms.includes("fraktur")) { return mapChars(fraktur, text); } return text; } /** * @param {string} text * @param {string[]} transforms * @returns {string} */ export function literalValue(text, transforms) { if (transforms.includes("bold")) { if (transforms.includes("italic")) { if (transforms.includes("sans-serif")) { return mapChars(boldItalicSansSerif, text); } return mapChars(boldItalic, text); } if (transforms.includes("script")) { return mapChars(boldScript, text); } if (transforms.includes("fraktur")) { return mapChars(boldFraktur, text); } if (transforms.includes("sans-serif")) { return mapChars(boldSansSerif, text); } return mapChars(bold, text); } if (transforms.includes("italic")) { if (transforms.includes("double-struck")) { return mapChars(italicDoubleStruck, text); } if (transforms.includes("sans-serif")) { return mapChars(italicSansSerif, text); } return mapChars(italic, text); } if (transforms.includes("sans-serif")) { return mapChars(sansSerif, text); } if (transforms.includes("monospace")) { return mapChars(monospace, text); } return textLiteralValue(text, transforms); }