├── .eslintrc.json ├── .gitignore ├── .prettierrc.json ├── LICENSE ├── README.md ├── index.html ├── package-lock.json ├── package.json ├── public └── favicon.ico ├── src ├── assets │ ├── github.svg │ ├── next.svg │ └── previous.svg ├── components │ ├── App │ │ ├── App.module.scss │ │ └── App.tsx │ ├── Canvas │ │ ├── Button │ │ │ ├── Button.module.scss │ │ │ └── Button.tsx │ │ ├── Canvas.module.scss │ │ ├── Canvas.tsx │ │ ├── OptionsMenu │ │ │ └── OptionsMenu.tsx │ │ └── ShapesMenu │ │ │ └── ShapesMenu.tsx │ ├── ConfirmModal │ │ ├── ConfirmModal.module.scss │ │ └── ConfirmModal.tsx │ ├── Glyph │ │ ├── Glyph.module.scss │ │ └── Glyph.tsx │ └── GlyphSet │ │ ├── GlyphSet.module.scss │ │ └── GlyphSet.tsx ├── fonts │ ├── ChicagoFLF.ttf │ └── Karektar.otf ├── index.scss ├── main.tsx ├── types.ts ├── utils │ ├── constants │ │ ├── app.constants.ts │ │ └── canvas.constants.ts │ ├── helpers │ │ ├── app.helpers.ts │ │ └── canvas.helpers.ts │ └── reducers │ │ └── fontReducer.ts ├── variables.scss └── vite-env.d.ts ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "eslint:recommended", 8 | "plugin:react/recommended", 9 | "plugin:react-hooks/recommended", 10 | "plugin:@typescript-eslint/recommended", 11 | "plugin:@typescript-eslint/recommended-requiring-type-checking", 12 | "prettier" 13 | ], 14 | "overrides": [], 15 | "parser": "@typescript-eslint/parser", 16 | "parserOptions": { 17 | "ecmaVersion": "latest", 18 | "sourceType": "module", 19 | "project": "./tsconfig.json" 20 | }, 21 | "settings": { 22 | "react": { 23 | "version": "detect" 24 | } 25 | }, 26 | "plugins": [ 27 | "react", 28 | "react-hooks", 29 | "@typescript-eslint", 30 | "etc", 31 | "import", 32 | "prettier" 33 | ], 34 | "rules": { 35 | "@typescript-eslint/ban-types": [ 36 | "error", 37 | { 38 | "types": { 39 | "String": { 40 | "message": "Use string instead", 41 | "fixWith": "string" 42 | }, 43 | "Boolean": { 44 | "message": "Use boolean instead", 45 | "fixWith": "boolean" 46 | }, 47 | "Number": { 48 | "message": "Use number instead", 49 | "fixWith": "number" 50 | }, 51 | "Symbol": { 52 | "message": "Use symbol instead", 53 | "fixWith": "symbol" 54 | }, 55 | "BigInt": { 56 | "message": "Use bigint instead", 57 | "fixWith": "bigint" 58 | }, 59 | "Function": { 60 | "message": "The `Function` type accepts any function-like value. It provides no type safety when calling the function, which can be a common source of bugs." 61 | } 62 | }, 63 | "extendDefaults": false 64 | } 65 | ], 66 | "@typescript-eslint/dot-notation": "off", 67 | "@typescript-eslint/member-delimiter-style": [ 68 | "error", 69 | { 70 | "multiline": { 71 | "delimiter": "none", 72 | "requireLast": true 73 | }, 74 | "singleline": { 75 | "delimiter": "semi", 76 | "requireLast": false 77 | } 78 | } 79 | ], 80 | "@typescript-eslint/naming-convention": [ 81 | "error", 82 | { 83 | "selector": "default", 84 | "format": ["camelCase"] 85 | }, 86 | { 87 | "selector": ["objectLiteralProperty"], 88 | "format": ["UPPER_CASE", "camelCase", "PascalCase", "snake_case"], 89 | "leadingUnderscore": "allow" 90 | }, 91 | { 92 | "selector": ["memberLike"], 93 | "format": ["UPPER_CASE", "camelCase", "PascalCase"], 94 | "leadingUnderscore": "allow" 95 | }, 96 | { 97 | "selector": ["classProperty"], 98 | "format": ["UPPER_CASE", "camelCase"], 99 | "leadingUnderscore": "allow" 100 | }, 101 | { 102 | "selector": ["variable", "parameter"], 103 | "format": ["UPPER_CASE", "camelCase", "PascalCase"], 104 | "leadingUnderscore": "allow" 105 | }, 106 | { 107 | "selector": ["typeLike"], 108 | "format": ["PascalCase", "UPPER_CASE"] 109 | }, 110 | { 111 | "selector": ["function"], 112 | "format": ["PascalCase", "camelCase"] 113 | }, 114 | { 115 | "selector": [ 116 | "classProperty", 117 | "objectLiteralProperty", 118 | "typeProperty", 119 | "classMethod", 120 | "objectLiteralMethod", 121 | "typeMethod", 122 | "accessor", 123 | "enumMember" 124 | ], 125 | "format": null, 126 | "modifiers": ["requiresQuotes"] 127 | }, 128 | // We exclude identifiers with names starting with `__UNSAFE_` so that they may 129 | // be used without breaking linting. 130 | { 131 | "selector": ["variable", "parameter", "memberLike", "objectLiteralProperty"], 132 | "format": null, 133 | "filter": { 134 | "regex": "^__UNSAFE_", 135 | "match": true 136 | } 137 | } 138 | ], 139 | "@typescript-eslint/no-empty-function": "error", 140 | "@typescript-eslint/no-floating-promises": "error", 141 | "@typescript-eslint/no-redeclare": ["error"], 142 | "@typescript-eslint/no-shadow": [ 143 | "error", 144 | { 145 | "hoist": "all" 146 | } 147 | ], 148 | "@typescript-eslint/no-unused-expressions": [ 149 | "error", 150 | { 151 | "allowShortCircuit": true, 152 | "allowTernary": true 153 | } 154 | ], 155 | "@typescript-eslint/semi": ["error", "never"], 156 | "@typescript-eslint/type-annotation-spacing": "error", 157 | "brace-style": ["error", "1tbs"], 158 | "comma-dangle": "off", 159 | "curly": "error", 160 | "default-case": "error", 161 | "dot-notation": "off", 162 | "eol-last": "off", 163 | "eqeqeq": ["error", "smart"], 164 | "guard-for-in": "error", 165 | "id-match": "error", 166 | "import/order": [ 167 | "warn", 168 | { 169 | "alphabetize": {"order": "asc", "caseInsensitive": true}, 170 | "pathGroups": [ 171 | { 172 | "pattern": "~/**", 173 | "group": "internal", 174 | "position": "after" 175 | } 176 | ], 177 | "groups": ["builtin", "external", "internal", ["parent", "sibling"]] 178 | } 179 | ], 180 | "no-bitwise": "error", 181 | "no-caller": "error", 182 | "no-cond-assign": ["error", "always"], 183 | "no-console": [ 184 | "off", 185 | { 186 | "allow": [ 187 | "warn", 188 | "dir", 189 | "timeLog", 190 | "assert", 191 | "clear", 192 | "count", 193 | "countReset", 194 | "group", 195 | "groupEnd", 196 | "table", 197 | "dirxml", 198 | "groupCollapsed", 199 | "Console", 200 | "profile", 201 | "profileEnd", 202 | "timeStamp", 203 | "context" 204 | ] 205 | } 206 | ], 207 | "no-debugger": "error", 208 | "no-empty": [ 209 | "error", 210 | { 211 | "allowEmptyCatch": true 212 | } 213 | ], 214 | // no-empty-function is turned off because we are using the 215 | // incompatible rule provided by @typescript-eslint/no-empty-function 216 | "no-constant-condition": ["error", {"checkLoops": false}], 217 | "no-empty-function": "off", 218 | "no-eval": "error", 219 | "no-multiple-empty-lines": "error", 220 | "no-new-wrappers": "error", 221 | "no-trailing-spaces": "off", 222 | "no-unused-labels": "error", 223 | "radix": "error", 224 | "react/display-name": "off", 225 | "react/jsx-boolean-value": "off", 226 | "react/jsx-curly-brace-presence": ["error", "never"], 227 | "react/jsx-curly-spacing": "off", 228 | "react/jsx-equals-spacing": ["error", "never"], 229 | "react/jsx-key": "error", 230 | "react/no-children-prop": "off", 231 | "react/no-danger": "error", 232 | "react/no-unescaped-entities": "off", 233 | "react/prop-types": "off", 234 | "react/jsx-no-bind": "off", 235 | "react/jsx-wrap-multilines": "off", 236 | "react/self-closing-comp": "error", 237 | "semi": ["error", "never"], 238 | "sort-imports": [ 239 | "warn", 240 | { 241 | "ignoreCase": true, 242 | "ignoreDeclarationSort": true 243 | } 244 | ], 245 | "spaced-comment": [ 246 | "error", 247 | "always", 248 | { 249 | "markers": ["/"] 250 | } 251 | ], 252 | "no-fallthrough": ["error", {"allowEmptyCase": true}], 253 | "etc/no-implicit-any-catch": ["error", {"allowExplicitAny": false}], 254 | "react/react-in-jsx-scope": "off" 255 | } 256 | } 257 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": false, 3 | "singleQuote": true, 4 | "quoteProps": "consistent", 5 | "printWidth": 85, 6 | "trailingComma": "all", 7 | "bracketSpacing": false, 8 | "endOfLine": "lf", 9 | "arrowParens": "avoid" 10 | } 11 | 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 Nishanth Jayram 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Karektar 2 | 3 | > Karektar is a web application for building exportable bitmap fonts from custom glyph sets. 4 | 5 | https://karektar.newtrino.ink/ 6 | 7 | ## Features 8 | * Design fonts for specific use cases, without needing to build all possible glyphs. 9 | * Utilize various tools for drawing on the canvas, from the basic pencil/eraser to shape and fill tools. 10 | * Export fonts to an OTF format (utilizing the [opentype.js](https://github.com/opentypejs/opentype.js) library). 11 | 12 | ## Setup 13 | * Clone the repository: `git clone https://github.com/nishanthjayram/karektar.git`. 14 | * Install project dependencies by running `yarn`. 15 | * Start the development server by running `yarn dev`. (Run `yarn dev --host` to expose to other devices on your local network.) 16 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | 6 | 7 |95 | © 2023 by{' '} 96 | 97 | Nishanth Jayram 98 | 99 | . All rights reserved. 100 |
101 | 106 |