feat: add support for Kotlin language and themes, enhance language detection for various programming languages

This commit is contained in:
2026-01-07 16:56:03 +02:00
parent 05b80d1e0c
commit 6510dac3bc
4 changed files with 91 additions and 28 deletions
+4
View File
@@ -5,6 +5,10 @@
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🖼️</text></svg>" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Code Canvas - Create Beautiful Code Images</title>
<!-- Google Fonts: JetBrains Mono, Fira Code, Source Code Pro for code -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600;700&family=Source+Code+Pro:wght@400;500;600;700&family=Inter:wght@400;500;600;700&family=Roboto:wght@400;500;700&display=swap" rel="stylesheet">
</head>
<body>
<div id="root"></div>
+9 -3
View File
@@ -235,9 +235,15 @@ export const createCodeElement = (x: number, y: number): CodeElement => ({
locked: false,
visible: true,
props: {
code: '// Your code here\nconsole.log("Hello, World!");',
language: 'javascript',
theme: 'github-dark',
code: `@Composable
fun Greeting(name: String) {
Text(
text = "Hello, $name!",
modifier = Modifier.padding(16.dp)
)
}`,
language: 'kotlin',
theme: 'dracula',
fontFamily: 'JetBrains Mono',
fontSize: 14,
lineHeight: 1.5,
+30 -16
View File
@@ -125,44 +125,58 @@ export const ASPECT_RATIOS: AspectRatio[] = [
];
export const CODE_THEMES = [
{ id: 'github-dark', name: 'GitHub Dark', bg: '#0d1117' },
{ id: 'github-light', name: 'GitHub Light', bg: '#ffffff' },
// IntelliJ / Android Studio themes (prioritized for Kotlin)
{ id: 'andromeeda', name: 'Andromeeda', bg: '#23262e' },
{ id: 'aurora-x', name: 'Aurora X', bg: '#07090f' },
{ id: 'dark-plus', name: 'Dark+ (VS Code)', bg: '#1e1e1e' },
{ id: 'dracula', name: 'Dracula', bg: '#282a36' },
{ id: 'nord', name: 'Nord', bg: '#2e3440' },
{ id: 'one-dark-pro', name: 'One Dark Pro', bg: '#282c34' },
{ id: 'monokai', name: 'Monokai', bg: '#272822' },
{ id: 'tokyo-night', name: 'Tokyo Night', bg: '#1a1b26' },
{ id: 'vitesse-dark', name: 'Vitesse Dark', bg: '#121212' },
{ id: 'vitesse-light', name: 'Vitesse Light', bg: '#ffffff' },
{ id: 'dracula-soft', name: 'Dracula Soft', bg: '#282a36' },
{ id: 'github-dark', name: 'GitHub Dark', bg: '#0d1117' },
{ id: 'github-dark-dimmed', name: 'GitHub Dimmed', bg: '#22272e' },
{ id: 'github-light', name: 'GitHub Light', bg: '#ffffff' },
{ id: 'light-plus', name: 'Light+ (VS Code)', bg: '#ffffff' },
{ id: 'material-theme-darker', name: 'Material Darker', bg: '#212121' },
{ id: 'catppuccin-mocha', name: 'Catppuccin Mocha', bg: '#1e1e2e' },
{ id: 'catppuccin-latte', name: 'Catppuccin Latte', bg: '#eff1f5' },
{ id: 'slack-dark', name: 'Slack Dark', bg: '#222222' },
{ id: 'poimandres', name: 'Poimandres', bg: '#1b1e28' },
{ id: 'night-owl', name: 'Night Owl', bg: '#011627' },
{ id: 'material-theme-ocean', name: 'Material Ocean', bg: '#0f111a' },
{ id: 'material-theme-palenight', name: 'Material Palenight', bg: '#292d3e' },
{ id: 'min-dark', name: 'Min Dark', bg: '#1f1f1f' },
{ id: 'min-light', name: 'Min Light', bg: '#ffffff' },
{ id: 'ayu-dark', name: 'Ayu Dark', bg: '#0b0e14' },
{ id: 'monokai', name: 'Monokai', bg: '#272822' },
{ id: 'night-owl', name: 'Night Owl', bg: '#011627' },
{ id: 'nord', name: 'Nord', bg: '#2e3440' },
{ id: 'one-dark-pro', name: 'One Dark Pro', bg: '#282c34' },
{ id: 'poimandres', name: 'Poimandres', bg: '#1b1e28' },
{ id: 'rose-pine', name: 'Rosé Pine', bg: '#191724' },
{ id: 'rose-pine-moon', name: 'Rosé Pine Moon', bg: '#232136' },
{ id: 'slack-dark', name: 'Slack Dark', bg: '#222222' },
{ id: 'solarized-dark', name: 'Solarized Dark', bg: '#002b36' },
{ id: 'solarized-light', name: 'Solarized Light', bg: '#fdf6e3' },
{ id: 'tokyo-night', name: 'Tokyo Night', bg: '#1a1b26' },
{ id: 'vesper', name: 'Vesper', bg: '#101010' },
{ id: 'vitesse-dark', name: 'Vitesse Dark', bg: '#121212' },
{ id: 'vitesse-light', name: 'Vitesse Light', bg: '#ffffff' },
] as const;
export type CodeThemeId = typeof CODE_THEMES[number]['id'];
export const LANGUAGES = [
'javascript',
'typescript',
'kotlin',
'java',
'typescript',
'javascript',
'python',
'rust',
'go',
'swift',
'dart',
'html',
'css',
'json',
'xml',
'yaml',
'markdown',
'bash',
'sql',
'groovy',
] as const;
export const FONT_FAMILIES = {
+48 -9
View File
@@ -62,7 +62,7 @@ export function getThemeBackground(themeId: CodeThemeId): string {
}
export function isLightTheme(themeId: CodeThemeId): boolean {
const lightThemes = ['github-light', 'vitesse-light', 'catppuccin-latte', 'min-light', 'solarized-light'];
const lightThemes = ['github-light', 'vitesse-light', 'min-light', 'solarized-light', 'light-plus'];
return lightThemes.includes(themeId);
}
@@ -81,6 +81,22 @@ export function isHighlighterReady(): boolean {
// Language detection based on common patterns
export function detectLanguage(code: string): string {
// Kotlin / Jetpack Compose patterns (check first - primary language)
// Look for @Composable, fun, val, var, class, object, companion object, etc.
if (/@Composable|@Preview|@OptIn|@Suppress/.test(code)) {
return 'kotlin';
}
if (/^(fun|val|var|class|object|package|import|sealed|data\s+class|enum\s+class|interface)\s+/m.test(code)) {
// Check for Kotlin-specific syntax
if (/:\s*\w+(\s*[={()]|$|\s*,)/.test(code) ||
/\b(Unit|String|Int|Long|Boolean|Float|Double|List|Map|Set|suspend|inline|crossinline|noinline|reified)\b/.test(code) ||
/\bModifier\b|\bColumn\b|\bRow\b|\bBox\b|\bText\b|\bButton\b|\bScaffold\b/.test(code) ||
/\.copy\(|\.let\s*\{|\.apply\s*\{|\.also\s*\{|\.run\s*\{/.test(code) ||
/\blambda\b|->/.test(code)) {
return 'kotlin';
}
}
// TypeScript/JavaScript patterns
if (/^import\s+.*from\s+['"]|^export\s+(default\s+)?|const\s+\w+:\s*\w+/m.test(code)) {
if (/:\s*(string|number|boolean|any|void|Promise|Array)\b/.test(code)) {
@@ -89,15 +105,22 @@ export function detectLanguage(code: string): string {
return 'javascript';
}
// Kotlin patterns
if (/^(fun|val|var|class|object|package|import)\s+/m.test(code) &&
/:\s*\w+(\s*=|\s*\{|\s*\))/.test(code)) {
return 'kotlin';
// Java patterns (after Kotlin to avoid false positives)
if (/^(public|private|protected)\s+(static\s+)?(class|void|int|String)/m.test(code) &&
!/@Composable/.test(code)) {
return 'java';
}
// Java patterns
if (/^(public|private|protected)\s+(static\s+)?(class|void|int|String)/m.test(code)) {
return 'java';
// Swift patterns
if (/^(func|var|let|class|struct|enum|protocol|import\s+\w+)\s+/m.test(code) &&
/@State|@Binding|@Published|@ObservedObject|some\s+View/.test(code)) {
return 'swift';
}
// Dart/Flutter patterns
if (/^(class|void|final|const|import\s+')/m.test(code) &&
/Widget|StatelessWidget|StatefulWidget|BuildContext|setState/.test(code)) {
return 'dart';
}
// Python patterns
@@ -115,6 +138,17 @@ export function detectLanguage(code: string): string {
return 'go';
}
// Groovy/Gradle patterns
if (/^(plugins|dependencies|android|buildscript)\s*\{/m.test(code) ||
/implementation\s*\(|compile\s*\(/.test(code)) {
return 'groovy';
}
// XML patterns (for Android layouts)
if (/^<\?xml|^<resources|^<layout|^<LinearLayout|^<RelativeLayout|^<ConstraintLayout|^<androidx\./m.test(code)) {
return 'xml';
}
// HTML patterns
if (/^<!DOCTYPE|<html|<head|<body|<div/m.test(code)) {
return 'html';
@@ -135,6 +169,11 @@ export function detectLanguage(code: string): string {
}
}
// YAML patterns
if (/^[\w-]+:\s*(\n|$)|^\s+-\s+/m.test(code) && !/[{};]/.test(code)) {
return 'yaml';
}
// SQL patterns
if (/^(SELECT|INSERT|UPDATE|DELETE|CREATE|DROP|ALTER)\s+/im.test(code)) {
return 'sql';
@@ -145,5 +184,5 @@ export function detectLanguage(code: string): string {
return 'bash';
}
return 'javascript'; // Default
return 'kotlin'; // Default to Kotlin as primary language
}