|
| 1 | +const GLYPH_COLUMNS = 3 |
| 2 | +const GLYPH_ROWS = 5 |
| 3 | +const PIXEL = '█' |
| 4 | + |
| 5 | +const FONT: Record<string, readonly string[]> = { |
| 6 | + ' ': ['000', '000', '000', '000', '000'], |
| 7 | + '-': ['000', '000', '111', '000', '000'], |
| 8 | + '!': ['010', '010', '010', '000', '010'], |
| 9 | + '?': ['111', '001', '011', '000', '010'], |
| 10 | + '0': ['111', '101', '101', '101', '111'], |
| 11 | + '1': ['010', '110', '010', '010', '111'], |
| 12 | + '2': ['111', '001', '111', '100', '111'], |
| 13 | + '3': ['111', '001', '111', '001', '111'], |
| 14 | + '4': ['101', '101', '111', '001', '001'], |
| 15 | + '5': ['111', '100', '111', '001', '111'], |
| 16 | + '6': ['111', '100', '111', '101', '111'], |
| 17 | + '7': ['111', '001', '010', '010', '010'], |
| 18 | + '8': ['111', '101', '111', '101', '111'], |
| 19 | + '9': ['111', '101', '111', '001', '111'], |
| 20 | + A: ['010', '101', '111', '101', '101'], |
| 21 | + B: ['110', '101', '110', '101', '110'], |
| 22 | + C: ['011', '100', '100', '100', '011'], |
| 23 | + D: ['110', '101', '101', '101', '110'], |
| 24 | + E: ['111', '100', '110', '100', '111'], |
| 25 | + F: ['111', '100', '110', '100', '100'], |
| 26 | + G: ['011', '100', '101', '101', '011'], |
| 27 | + H: ['101', '101', '111', '101', '101'], |
| 28 | + I: ['111', '010', '010', '010', '111'], |
| 29 | + J: ['001', '001', '001', '101', '010'], |
| 30 | + K: ['101', '110', '100', '110', '101'], |
| 31 | + L: ['100', '100', '100', '100', '111'], |
| 32 | + M: ['111', '111', '101', '101', '101'], |
| 33 | + N: ['101', '111', '111', '111', '101'], |
| 34 | + O: ['010', '101', '101', '101', '010'], |
| 35 | + P: ['110', '101', '110', '100', '100'], |
| 36 | + Q: ['010', '101', '101', '111', '011'], |
| 37 | + R: ['110', '101', '110', '110', '101'], |
| 38 | + S: ['011', '100', '010', '001', '110'], |
| 39 | + T: ['111', '010', '010', '010', '010'], |
| 40 | + U: ['101', '101', '101', '101', '111'], |
| 41 | + V: ['101', '101', '101', '010', '010'], |
| 42 | + W: ['101', '101', '101', '111', '111'], |
| 43 | + X: ['101', '010', '010', '010', '101'], |
| 44 | + Y: ['101', '101', '010', '010', '010'], |
| 45 | + Z: ['111', '001', '010', '100', '111'] |
| 46 | +} as const |
| 47 | + |
| 48 | +function renderBlock(text: string): string[] { |
| 49 | + const rows = Array.from({ length: GLYPH_ROWS }, () => '') |
| 50 | + for (const ch of text.toUpperCase()) { |
| 51 | + const glyph = FONT[ch] ?? FONT['?'] |
| 52 | + for (let r = 0; r < GLYPH_ROWS; r++) |
| 53 | + rows[r] += `${glyph[r].replace(/1/g, PIXEL).replace(/0/g, ' ')} ` |
| 54 | + } |
| 55 | + return rows.map(r => r.trimEnd()) |
| 56 | +} |
| 57 | + |
| 58 | +function widthOf(text: string): number { |
| 59 | + const perChar = GLYPH_COLUMNS + 1 // 3 pixels + 1 space |
| 60 | + return Math.max(0, text.length * perChar - 1) |
| 61 | +} |
| 62 | + |
| 63 | +function wrapByWords(text: string, maxWidth: number): string[] { |
| 64 | + const words = text.trim().split(/\s+/) |
| 65 | + const lines: string[] = [] |
| 66 | + const perChar = GLYPH_COLUMNS + 1 |
| 67 | + const spaceChars = 1 |
| 68 | + |
| 69 | + let currentChars = 0 |
| 70 | + let currentWords: string[] = [] |
| 71 | + |
| 72 | + for (const word of words) { |
| 73 | + const nextChars = (currentWords.length ? spaceChars : 0) + word.length |
| 74 | + const nextWidth = (currentChars + nextChars) * perChar - 1 |
| 75 | + if (currentWords.length && nextWidth > maxWidth) { |
| 76 | + lines.push(currentWords.join(' ')) |
| 77 | + currentWords = [word] |
| 78 | + currentChars = word.length |
| 79 | + } else { |
| 80 | + currentWords.push(word) |
| 81 | + currentChars += nextChars |
| 82 | + } |
| 83 | + } |
| 84 | + if (currentWords.length) lines.push(currentWords.join(' ')) |
| 85 | + return lines |
| 86 | +} |
| 87 | + |
| 88 | +export function heading(text: string): void { |
| 89 | + const columns = process.stdout.columns ?? 80 |
| 90 | + |
| 91 | + const words = text.trim().split(/\s+/) |
| 92 | + if (Math.max(...words.map(w => widthOf(w))) > columns) { |
| 93 | + process.stdout.write(`\n${text}\n\n`) |
| 94 | + return |
| 95 | + } |
| 96 | + |
| 97 | + const lines = wrapByWords(text, columns) |
| 98 | + |
| 99 | + process.stdout.write('\n') |
| 100 | + lines.forEach((line, index) => { |
| 101 | + const block = renderBlock(line) |
| 102 | + process.stdout.write(`${block.join('\n')}\n`) |
| 103 | + if (index < lines.length - 1) { |
| 104 | + const w = Math.min(widthOf(line), columns) |
| 105 | + process.stdout.write(`${'─'.repeat(w)}\n`) |
| 106 | + } |
| 107 | + }) |
| 108 | + process.stdout.write('\n') |
| 109 | +} |
| 110 | + |
| 111 | +heading('create rubric app') |
0 commit comments