diff --git a/hatenablog/css/pseudocode.css b/hatenablog/css/pseudocode.css new file mode 100644 index 00000000..d666d65e --- /dev/null +++ b/hatenablog/css/pseudocode.css @@ -0,0 +1,101 @@ +body { + font-family: Lato; +} +button { + font-family: Lato; + color: #5f6368; + background-color: white; + border-radius: 4px; + border: 1px solid #dadce0; +} +button:focus { + color: white; + background-color: #ffadba; + border-radius: 4px; + border: 1px solid #ffadba; + font-weight: bold; + outline: 2px solid rgba(255, 173, 186, 0.7); +} +.error { + font-family: monospace; + color: #a11 +} +.frame-wrapper { + position: relative; +} +.frame-wrapper .text-label { + background-color: white; + box-shadow: 5px 0 0 white,-5px 0 0 white; + color: #5f6368; + display: inline; + font-size: 12px; + left: 12px; + margin-bottom: 0; + max-width: 300px; + padding: 0; + position: absolute; + top: 0px; + word-wrap: break-word; +} +.frame-wrapper .div-label { + background-color: white; + box-shadow: 5px 0 0 white,-5px 0 0 white; + color: #5f6368; + display: inline; + font-size: 12px; + left: 12px; + margin-bottom: 0; + max-width: 300px; + padding: 0; + position: absolute; + top: -8px; + word-wrap: break-word; +} +.frame-wrapper .text-label.focused, .frame-wrapper .div-label.focused { + color: #ffadba; +} +.frame-wrapper .textinput { + border: 1px solid #dadce0; + border-radius: 4px; + box-sizing: border-box; + color: #3c4043; + font-family: 'TheSansMonoCd',monospace; + font-weight: 450; + font-size: 14px; + margin: 8px 0; +} +.frame-wrapper input.textinput { + padding: 1px 8px; + height: 36px; +} +.frame-wrapper textarea.textinput { + padding: 8px; + min-height: 36px; + width: 100%; +} +.frame-wrapper div.textinput { + padding: 8px; + min-height: 30px; +} +.frame-wrapper .textinput:focus { + border: 1px solid #ffadba; + outline: 1px solid rgba(255, 173, 186, 0.7); +} + +.pseudocode { + -moz-border-radius: 3px; + -moz-box-shadow: 0 2px 0 rgba(34,58,112,0.4),0 0 0 2px #fff inset; + -webkit-border-radius :3px; + -webkit-box-shadow: 0 2px 0 rgba(34,58,112,0.4),0 0 0 2px #fff inset; + background-color: #eee; + border: 1px solid #ccc; + border-radius: 5px; + box-shadow: 0 2px 0 rgba(34,58,112,0.4),0 0 0 2px #fff inset; + color: #333; + display: block; + font-family: Menlo, monospace; + line-height: 1.4; + margin: .4em .1em; + padding: .8em 1.8em; + text-shadow: 0 0px 0 #fff; +} diff --git a/hatenablog/js/pseudocode.js b/hatenablog/js/pseudocode.js new file mode 100644 index 00000000..3827005d --- /dev/null +++ b/hatenablog/js/pseudocode.js @@ -0,0 +1,86 @@ +function markup(code, elt) { + let out = ""; + let indent = [0]; + let lineno = 0; + let opening = false; + let opened = false; + let keep = false; + + const RE = /(?:\$(?(?:\\.|[^$\\])+)\$)|(?[\w-]+)/g; + const INDENT = /^ */; + + for (const line of code.split("\n")) { + let cur = ""; + let len = line.match(INDENT)[0].length; + if (len === line.length) { + continue; + } + + if (indent[indent.length - 1] < len) { + indent.push(len); + } else if (indent[indent.length - 1] > len) { + indent.pop(); + } + + for (const match of line.matchAll(RE)) { + if (cur !== "") { + cur += "\\;"; + } + + if (match.groups.TEX) { + cur += `${match.groups.TEX}`; + } else if (match.groups.KW) { + if (["function"].includes(match.groups.KW)) { + opening = true; + } + if (/\b(?:const|let|global)/.test(match.groups.KW)) { + keep = true; + } + cur += `\\text{\\textbf{${match.groups.KW}}}`; + } + } + + if (cur !== "") { + if (lineno > 0) { + out += "\\\\\n"; + } + + if (keep) { + out += " \\quad".repeat(indent.length - 1); + } else if (!opening) { + out += `\\qquad {\\scriptsize ${++lineno}}{\\scriptsize\\colon} &`; + out += " \\quad".repeat(indent.length - 1); + } + if (opening) { + if (opened) { + out += "\n\\end{aligned}\\\\\n"; + lineno = 0; + } + out += `& ${cur} \\\\\n`; + out += "&\\begin{aligned}\n"; + opened = true; + } else if (keep && !opened) { + out += `& ${cur} \\\\\n`; + } else { + out += ` ${cur}`; + } + } + + keep = false; + opening = false; + } + + if (opened) { + out += "\n\\end{aligned}"; + } + + elt.outerHTML = katex.renderToString(`
\\begin{aligned}${out}\\end{aligned}
`, { macros }); +} + +document.addEventListener('DOMContentLoaded', (() => { + [...document.getElementsByTagName('pre')] + .filter((pre) => pre.getAttribute('data-lang') === 'pseudocode') + .forEach((pre) => { + markup(pre.innerText, pre); + }); +}));