From bee54517f948168407cbd70cb836e6b1279dddc3 Mon Sep 17 00:00:00 2001 From: "claude[bot]" <209825114+claude[bot]@users.noreply.github.com> Date: Fri, 27 Jun 2025 00:17:34 +0000 Subject: [PATCH] feat: add hiragana character support to ASCII art MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Added 25+ hiragana characters to BLOCK_FONT dictionary (あいうえお, かきくけこ, さしすせそ, たちつてと, なにぬねの) - Modified textToAsciiArt function to preserve hiragana characters (no uppercase conversion) - Added comprehensive tests for hiragana functionality including mixed text support - Hiragana characters are now rendered in ASCII art format alongside existing English/numeric support Co-authored-by: watany --- src/fonts.ts | 39 ++++++++++++++++++++++++++++---- test/fonts.test.ts | 56 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 4 deletions(-) diff --git a/src/fonts.ts b/src/fonts.ts index ae20276..88a8195 100644 --- a/src/fonts.ts +++ b/src/fonts.ts @@ -42,8 +42,39 @@ export const BLOCK_FONT: Record = { "7": f(["███████╗", "╚════██║", " ██╔╝", " ██╔╝ ", " ██║ ", " ╚═╝ "]), "8": f([" █████╗ ", "██╔══██╗", "╚█████╔╝", "██╔══██╗", "╚█████╔╝", " ╚════╝ "]), "9": f([" █████╗ ", "██╔══██╗", "╚██████║", " ╚═══██║", " █████╔╝", " ╚════╝ "]), + // Hiragana characters + あ: f([" ▄████╗ ", "██╔═══╝ ", "██████╗ ", "██╔══██╗", "╚█████╔╝", " ╚════╝ "]), + い: f(["██╗ ███╗", "██║ ██╔╝", "██║ ██║ ", "██║ ██║ ", "██║ ██║ ", "╚═╝ ╚═╝ "]), + う: f([" ██████╗ ", "██╔════╝ ", "██████╗ ", "╚═══██║ ", " █████╔╝ ", " ╚════╝ "]), + え: f(["██████╗ ", "██╔═══╝ ", "██████╗ ", "██╔═══╝ ", "███████╗ ", "╚══════╝ "]), + お: f([" ██████╗ ", "██╔═══██╗", "██║ ██║", "██║ ██║", "╚██████╔╝", " ╚═════╝ "]), + か: f(["██╗ ███╗ ", "██║ ██╔╝ ", "██████╔╝ ", "██╔══██╗ ", "██║ ██║ ", "╚═╝ ╚═╝ "]), + き: f(["███╗ ██╗ ", "██╔╝ ██║ ", "██████╔╝ ", "██╔═██╗ ", "██║ ╚██╗ ", "╚═╝ ╚═╝ "]), + く: f([" ███╗ ", "██╔╝ ", "██║ ", "██║ ", "╚██╗ ", " ╚═╝ "]), + け: f(["██████╗ ", "██╔═══╝ ", "██████╗ ", "██╔══██╗ ", "██║ ██║ ", "╚═╝ ╚═╝ "]), + こ: f(["██████╗ ", "╚════██╗ ", " ██║ ", "██████╔╝ ", "╚═════╝ ", " "]), + さ: f(["███████╗ ", "██╔════╝ ", "███████╗ ", "╚════██║ ", "███████║ ", "╚══════╝ "]), + し: f([" ███╗ ", " ██╔╝ ", " ██╔╝ ", " ██╔╝ ", "██╔╝ ", "╚═╝ "]), + す: f([" ██████╗ ", "██╔════╝ ", "███████╗ ", "╚════██║ ", " █████╔╝ ", " ╚════╝ "]), + せ: f(["███████╗ ", "██╔════╝ ", "███████╗ ", "╚══██╔═╝ ", " ██║ ", " ╚═╝ "]), + そ: f([" ███████╗", "██╔════╝ ", "███████╗ ", "╚════██║ ", "█████╔╝ ", "╚════╝ "]), + た: f(["███████╗ ", "╚══██╔══╝", " ██║ ", " ██║ ", " ██║ ", " ╚═╝ "]), + ち: f(["███╗ ", "██╔╝ ", "██████╗ ", "╚═══██║ ", "████╔╝ ", "╚═══╝ "]), + つ: f([" ███████╗", "██╔════╝ ", "██║ ", "██║ ██╗", "╚██████╔╝", " ╚═════╝ "]), + て: f(["███████╗ ", "╚══════╝ ", "██╗ ", "██║ ", "╚██████╗ ", " ╚═════╝ "]), + と: f(["██╗ ██╗", "██║ ██║", "██║ ██║", "██║ ██║", "╚██████╔╝", " ╚═════╝ "]), + な: f(["███████╗ ", "██╔════╝ ", "██║ ███╗", "██║ ██║", "╚██████╔╝", " ╚═════╝ "]), + に: f(["███╗ ██╗", "██╔╝ ██║", "██║ ██║", "██║ ██║", "╚██████╔╝", " ╚═════╝ "]), + ぬ: f([" ██████╗ ", "██╔═══██╗", "██║ ██║", "██║▄▄ ██║", "╚██████╔╝", " ╚══▀▀═╝ "]), + ね: f([" ██████╗ ", "██╔════╝ ", "██║ ███╗", "██║ ██║", "╚██████╔╝", " ╚═════╝ "]), + の: f([" ██████╗ ", "██╔═══██╗", "██║ ██║", "██║ ██║", "╚██████╔╝", " ╚═════╝ "]), }; +function isHiragana(char: string): boolean { + const code = char.charCodeAt(0); + return code >= 0x3040 && code <= 0x309f; +} + export function textToAsciiArt(text: string): string { if (!text) return "\n\n\n\n\n"; const textLines = text.split("\n"); @@ -54,12 +85,12 @@ export function textToAsciiArt(text: string): string { if (!textLine) { for (let i = 0; i < 6; i++) allAsciiLines.push([]); } else { - const upperText = textLine.toUpperCase(); const lines: string[][] = [[], [], [], [], [], []]; const charArts: string[][] = []; - for (let charIndex = 0; charIndex < upperText.length; charIndex++) { - const char = upperText[charIndex]; - charArts[charIndex] = BLOCK_FONT[char] || spaceChar; + for (let charIndex = 0; charIndex < textLine.length; charIndex++) { + const char = textLine[charIndex]; + const lookupChar = isHiragana(char) ? char : char.toUpperCase(); + charArts[charIndex] = BLOCK_FONT[lookupChar] || spaceChar; } for (let i = 0; i < 6; i++) { for (let charIndex = 0; charIndex < charArts.length; charIndex++) { diff --git a/test/fonts.test.ts b/test/fonts.test.ts index 6f68b32..406edff 100644 --- a/test/fonts.test.ts +++ b/test/fonts.test.ts @@ -27,6 +27,14 @@ describe("fonts", () => { } }); + it("should contain basic hiragana characters", () => { + const hiragana = ["あ", "い", "う", "え", "お", "か", "き", "く", "け", "こ"]; + for (const char of hiragana) { + expect(BLOCK_FONT[char]).toBeDefined(); + expect(BLOCK_FONT[char]).toHaveLength(6); + } + }); + // Property-based testing for font consistency it("should maintain consistent character width across all letters", () => { const letters = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; @@ -281,6 +289,54 @@ describe("fonts", () => { } }); + it("should handle hiragana characters without case conversion", () => { + const hiraganaText = "あいう"; + const result = textToAsciiArt(hiraganaText); + const lines = result.split("\n"); + expect(lines).toHaveLength(6); + + // Hiragana should not be affected by case conversion + const expectedA = BLOCK_FONT["あ"]; + const expectedI = BLOCK_FONT["い"]; + const expectedU = BLOCK_FONT["う"]; + + for (let i = 0; i < 6; i++) { + const expectedLine = expectedA[i] + expectedI[i] + expectedU[i]; + expect(lines[i]).toBe(expectedLine); + } + }); + + it("should handle mixed hiragana and English text", () => { + const mixedText = "ABCあいう"; + const result = textToAsciiArt(mixedText); + const lines = result.split("\n"); + expect(lines).toHaveLength(6); + + // Should contain both English (converted to uppercase) and hiragana + const expectedA = BLOCK_FONT.A; + const expectedB = BLOCK_FONT.B; + const expectedC = BLOCK_FONT.C; + const expectedHiraganaA = BLOCK_FONT["あ"]; + const expectedHiraganaI = BLOCK_FONT["い"]; + const expectedHiraganaU = BLOCK_FONT["う"]; + + for (let i = 0; i < 6; i++) { + const expectedLine = + expectedA[i] + + expectedB[i] + + expectedC[i] + + expectedHiraganaA[i] + + expectedHiraganaI[i] + + expectedHiraganaU[i]; + expect(lines[i]).toBe(expectedLine); + } + }); + + it("should maintain visual consistency for hiragana text", () => { + const result = textToAsciiArt("こんにちは"); + expect(result).toMatchSnapshot(); + }); + // Additional comprehensive tests it("should produce reasonable output width for same-length strings", () => { const samples = ["AAAAA", "BBBBB", "CCCCC"];