Skip to content

dzikoysk/khangul

Repository files navigation

Khangul npm npm bundle size

Multiplatform Hangul processor library for Kotlin & JS projects, based on reverse-engineered Branah keyboard algorithm. Supported platforms:

  • JS (Node, Browser)
  • Kotlin (JVM, JS, Native)

Usage

Install khangul npm package:

$ npm i khangul

Import HangulContext:

import { HangulContext } from 'khangul'

Create a new instance of HangulContext and use it to process Hangul:

const hangulContext = new HangulContext()
console.log(hangulContext.getValue()) // ''

hangulContext.appendLetter('γ…‡')
hangulContext.appendLetter('ㅏ')
hangulContext.appendLetter('γ„΄')
console.log(hangulContext.getValue()) // μ•ˆ

hangulContext.removeLastLetter()
console.log(hangulContext.getValue()) // μ•„

Utilities

Each jamo is exposed as a Letter with character, name, romanization, and category. The Letters registry provides them as named constants and grouped lists:

Letters.γ„±.character     // 'γ„±'
Letters.γ„±.name          // 'giyeok'
Letters.γ„±.romanization  // ['g', 'k']

Letters.consonants       // [γ„±, γ„΄, γ„·, ...]
Letters.tenseConsonants  // [γ„², γ„Έ, γ…ƒ, ...]
Letters.vowels           // [ㅏ, γ…‘, γ…“, ...]
Letters.complexVowels    // [ㅐ, γ…”, γ…’, ...]

Letters.findByCharacter('γ…Ž')
Letters.getAll()
// output: [ ...consonants, ...tenseConsonants, ...vowels, ...complexVowels ]

Hangul.generateRandomSyllable()
// example output: λ²Ό, 으, λ·°, ν‹°, 이, 포, μ₯¬, 슜, 우

Visual recognition (experimental)

HangulRecognizer identifies hand-drawn jamo from canvas paths and validates guided stroke-by-stroke input. A live demo is available at dzikoysk.github.io/khangul.

import { Letters, experimental } from 'khangul'

const recognizer = new experimental.recognition.HangulRecognizer()

Free draw β€” pass an array of strokes (each stroke is a sequence of {x, y} points captured from a canvas) and get back a ranked list of letter matches with a 0–100 coverage score:

const paths = [[{x: 20, y: 25}, {x: 80, y: 25}, {x: 80, y: 80}]]
const results = recognizer.recognizeFromArrays(paths)
results[0].letter.character  // 'γ„±'
results[0].coverage          // 0–100

Guided mode β€” for tutorial-style UIs where the user is asked to draw a specific letter one step at a time. Validates that the points the user just drew match the expected shape and position of step stepIndex of the target letter, scaled to your canvas size in pixels:

recognizer.validateGuidedStrokeFromArray(userPoints, Letters.γ„±, 0, canvasSize)

Reference geometry β€” query the built-in stroke templates for any letter. Each letter is broken down into a list of "steps" (one pen-down/up action), and each step contains one or more connected segments. Useful for rendering guides or animating stroke order:

recognizer.getStepCount(Letters.γ„Ή)         // 3 β€” γ„Ή takes three pen strokes
recognizer.getStepSegments(Letters.γ„Ή, 0)   // segments drawn during step 0
recognizer.sampleReferenceStroke(stroke, 40) // 40 evenly-spaced points along a segment

Use-cases

The library handles natural Hangul input, so its main use-case is to be used in text editors, chat apps, etc. As an example, there's a preview of react-simple-keyboard component integrated with our Hangul context & compatibile keyboard layout:

import { HangulContext } from "khangul"
import Keyboard from 'react-simple-keyboard'

const hangulContext = useRef(new HangulContext())
const keyboard = useRef(null as unknown as SimpleKeyboard)
const [userInput, setUserInput] = useState('')
const [layoutName, setLayoutName] = useState('default')

const onKeyPress = (button: string) => {
    if (button === "{shift}" || button === "{lock}") {
        setLayoutName(layoutName === "default" ? "shift" : "default")
        return
    }
    else if (button === "{bksp}") {
        hangulContext.current.removeLastLetter()
        setUserInput(hangulContext.current.getValue())
        return
    }
    hangulContext.current.appendLetter(button)
    setUserInput(hangulContext.current.getValue())
}

return (
    <>
        <Input
            placeholder='Type here...'
            value={userInput}
            onChange={() => {}}
        />
        <Keyboard
            keyboardRef={ref => (keyboard.current = ref)}
            enableLayoutCandidates={false}
            layoutName={layoutName}
            onChange={() => {}}
            onKeyPress={onKeyPress}
            layout={{
                default: [
                    "` 1 2 3 4 5 6 7 8 9 0 - = {bksp}",
                    "{tab} γ…‚ γ…ˆ γ„· γ„± γ…… γ…› γ…• γ…‘ ㅐ γ…” [ ] \\",
                    "{lock} ㅁ γ„΄ γ…‡ γ„Ή γ…Ž γ…— γ…“ ㅏ γ…£ ; ' {enter}",
                    "{shift} γ…‹ γ…Œ γ…Š ㅍ γ…  γ…œ γ…‘ , . / {shift}",
                    ".com @ {space}",
                ],
                    shift: [
                    "~ ! @ # $ % ^ & * ( ) _ + {bksp}",
                    "{tab} γ…ƒ γ…‰ γ„Έ γ„² γ…†    γ…’ γ…– { } |",
                    '{lock}         : " {enter}',
                    "{shift} |       < > ? {shift}",
                    ".com @ {space}",
                ],
            }}
        />
    </>
)

About

Hangul processor for Kotlin Multiplatform & JavaScript projects, based on reverse-engineered enhanced version of Branah keyboard algorithm πŸ‡°πŸ‡·

Topics

Resources

License

Stars

Watchers

Forks

Contributors