diff --git a/.jules/bolt.md b/.jules/bolt.md new file mode 100644 index 0000000..97db95f --- /dev/null +++ b/.jules/bolt.md @@ -0,0 +1,3 @@ +## 2024-05-18 - Manual parsing avoids allocations +**Learning:** In Kotlin hot paths, replacing standard library string manipulations (`substring`, `toLong(16)`, `toString(16)`, `padStart`, `uppercase`) with explicit manual character array manipulation avoids intermediate `String` object allocations and measurably improves performance, especially when parsing small fixed-format structures like hex color strings used frequently in theme generation. +**Action:** Always prefer manual parsing logic that constructs from primitive character arrays or strings directly, instead of using fluent sequence of `String` conversion methods in hot paths like core algorithms. diff --git a/halogen-core/src/commonMain/kotlin/halogen/ThemeExpander.kt b/halogen-core/src/commonMain/kotlin/halogen/ThemeExpander.kt index 2c6099a..4cf3c7d 100644 --- a/halogen-core/src/commonMain/kotlin/halogen/ThemeExpander.kt +++ b/halogen-core/src/commonMain/kotlin/halogen/ThemeExpander.kt @@ -112,7 +112,17 @@ public object ThemeExpander { require(hex.startsWith("#") && hex.length == 7) { "Invalid hex color: \"$hex\". Expected format: #RRGGBB" } - val rgb = hex.substring(1).toLong(16).toInt() + var rgb = 0 + for (i in 1..6) { + val c = hex[i] + val digit = when { + c in '0'..'9' -> c - '0' + c in 'A'..'F' -> c - 'A' + 10 + c in 'a'..'f' -> c - 'a' + 10 + else -> throw IllegalArgumentException("Invalid hex color: \"$hex\". Expected format: #RRGGBB") + } + rgb = (rgb shl 4) or digit + } return rgb or (0xFF shl 24).toInt() } @@ -120,8 +130,15 @@ public object ThemeExpander { * Convert an ARGB integer to a hex color string like "#1A73E8". */ public fun argbToHex(argb: Int): String { - val rgb = argb and 0xFFFFFF - return "#" + rgb.toString(16).padStart(6, '0').uppercase() + val chars = CharArray(7) + chars[0] = '#' + var rgb = argb and 0xFFFFFF + for (i in 6 downTo 1) { + val nibble = rgb and 0xF + chars[i] = if (nibble < 10) (nibble + '0'.code).toChar() else (nibble - 10 + 'A'.code).toChar() + rgb = rgb shr 4 + } + return chars.concatToString() } private fun buildScheme(palette: HalogenPalette, isDark: Boolean): HalogenColorScheme {