seveibar/keyboard-utils

Based on the code provided, here's a concise summary of the electronic component: A keyboard utility library for parsing and generating keyboard layouts using the Keyboard Layout Editor (KLE) JSON format, with support for generating key matrices, reference designators, and diode routing for keyboard PCBs. Key features: - Parses KLE JSON layouts into key coordinate and metadata objects - Supports various keyboard layouts (60%, ANSI, ISO, Ergodox, etc.) - Generates unique reference designators for keys - Provides utility for adding diode routing in key matrices - TypeScript-based with React and tscircuit integration The library allows keyboard designers to easily convert layout designs into structured, machine-readable keyboard specifications.

Version
0.0.19
License
unset
Stars
3

lib/KLELayout.tsx

// Define the types for keyboard-layout-editor.com JSON format

import { getRefDesForKey } from "./getRefDesForKey"
import { assignRowColByPosition } from "./assignRowColByPosition"

export type KLEKey =
  | string
  | {
      x?: number
      y?: number
      w?: number
      h?: number
      x2?: number
      y2?: number
      w2?: number
      h2?: number
      r?: number
      rx?: number
      ry?: number
      n?: boolean
      l?: boolean
      d?: boolean
      g?: boolean
      sm?: string
      sb?: string
      st?: string
      a?: number
    }

export type KLELayout = KLEKey[][]
// Helper function to parse keyboard-layout-editor JSON

export const parseKLELayout = (layout: KLELayout) => {
  // Default key size in millimeters
  const KEY_SIZE = 19.05 // Standard keycap size (19.05mm or 0.75 inches)
  const keys: Array<{
    name: string
    x: number
    y: number
    width: number
    height: number
    rotation: number
    rotationX: number
    rotationY: number
    row: number
    col: number
  }> = []

  let current = {
    x: 0, y: 0, x2: 0, y2: 0,
    width: 1, height: 1, width2: 1, height2: 1,
    rotation_angle: 0, rotation_x: 0, rotation_y: 0,
    labels: [], textColor: [], textSize: [],
    default: { textColor: "#000000", textSize: 3 },
    color: "#cccccc", profile: "", nub: false,
    ghost: false, stepped: false, decal: false,
    sm: "", sb: "", st: ""
  }
  let cluster = { x: 0, y: 0 }
  let align = 4
  let keyId = 0

  const nameCounters = new Map<string, number>()

  // Process each row
  for (let r = 0; r < layout.length; r++) {
    if (layout[r] instanceof Array) {
      let colIndex = 0

      // Process each item in the row
      for (let k = 0; k < layout[r].length; k++) {
        const item = layout[r][k]

        if (typeof item === 'string') {
          // This is a key
          let refDes = getRefDesForKey(item)

          if (nameCounters.has(refDes)) {
            // Edit the original item to include the counter (if it doesn't have it)
            const ogKey = keys.find((k) => k.name === refDes)
            if (ogKey) {
              ogKey.name = `${refDes}${nameCounters.get(refDes)!}`
            }

            nameCounters.set(refDes, nameCounters.get(refDes)! + 1)
            refDes = `${refDes}${nameCounters.get(refDes)!}`
          } else {
            nameCounters.set(refDes, 1)
          }

          const newKey = {
            name: refDes,
            x: (current.x + current.width / 2) * KEY_SIZE,
            y: -(current.y + current.height / 2) * KEY_SIZE,
            width: current.width * KEY_SIZE,
            height: current.height * KEY_SIZE,
            rotation: current.rotation_angle,
            rotationX: current.rotation_x * KEY_SIZE,
            rotationY: -current.rotation_y * KEY_SIZE,
            row: r,
            col: colIndex,
          }

          keys.push(newKey)

          // Set up for the next key (from KLE logic)
          current.x += current.width
          current.width = current.height = 1
          current.x2 = current.y2 = current.width2 = current.height2 = 0
          current.nub = current.stepped = current.decal = false
          colIndex++

        } else {
          // This is a property object
          if (item.r != null) {
            if (k != 0) throw new Error("'r' can only be used on the first key in a row")
            current.rotation_angle = item.r
          }
          if (item.rx != null) {
            if (k != 0) throw new Error("'rx' can only be used on the first key in a row")
            current.rotation_x = cluster.x = item.rx
            // Extend current with cluster (reset position to rotation center)
            current.x = cluster.x
            current.y = cluster.y
          }
          if (item.ry != null) {
            if (k != 0) throw new Error("'ry' can only be used on the first key in a row")
            current.rotation_y = cluster.y = item.ry
            // Extend current with cluster (reset position to rotation center)
            current.x = cluster.x
            current.y = cluster.y
          }
          if (item.a != null) { align = item.a }
          if (item.x) { current.x += item.x }
          if (item.y) { current.y += item.y }
          if (item.w) { current.width = current.width2 = item.w }
          if (item.h) { current.height = current.height2 = item.h }
          // ... other properties would go here
        }
      }

      // End of the row (from KLE logic)
      current.y++
    }
    // Reset x to rotation_x at end of row (from KLE logic)
    current.x = current.rotation_x
  }

  // Apply logical row/col assignment based on physical positions
  return assignRowColByPosition(keys)
}