From e5b31f3750baf2b497d465566e76afda32b1b1bd Mon Sep 17 00:00:00 2001 From: Terry Yiu Date: Thu, 10 Sep 2026 09:49:12 +0300 Subject: [PATCH] Hide flag emoji on the web target, where they render as empty boxes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Compose for Web (js/wasmJs) draws everything through Skia onto a rather than through the browser's own text stack, so it has no color-emoji font to fall back to — a flag's regional-indicator codepoints render as empty tofu boxes instead of a flag. Reproduced via a headless-Chrome screenshot of the running dev server. Adds a per-platform supportsFlagEmoji() (true everywhere except js/wasmJs) that currencyFlagEmoji() now checks first, so currency rows on web just show the plain code/name with no flag rather than a broken glyph. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01FXcrACM5sok3M1KQJ8kByy --- .../kotlin/xyz/tyiu/satsprice/SystemCurrencies.android.kt | 2 ++ .../kotlin/xyz/tyiu/satsprice/SystemCurrencies.apple.kt | 2 ++ .../commonMain/kotlin/xyz/tyiu/satsprice/CurrencyFlag.kt | 7 +++++-- .../kotlin/xyz/tyiu/satsprice/SystemCurrencies.kt | 7 +++++++ .../kotlin/xyz/tyiu/satsprice/SystemCurrencies.jvm.kt | 2 ++ .../kotlin/xyz/tyiu/satsprice/SystemCurrencies.web.kt | 5 +++++ 6 files changed, 23 insertions(+), 2 deletions(-) diff --git a/shared/src/androidMain/kotlin/xyz/tyiu/satsprice/SystemCurrencies.android.kt b/shared/src/androidMain/kotlin/xyz/tyiu/satsprice/SystemCurrencies.android.kt index 3a19597..6f0878e 100644 --- a/shared/src/androidMain/kotlin/xyz/tyiu/satsprice/SystemCurrencies.android.kt +++ b/shared/src/androidMain/kotlin/xyz/tyiu/satsprice/SystemCurrencies.android.kt @@ -43,3 +43,5 @@ actual fun regionDisplayName(regionCode: String): String? { if (regionCode !in Locale.getISOCountries()) return null return Locale("", regionCode).getDisplayCountry(Locale.getDefault()) } + +actual fun supportsFlagEmoji(): Boolean = true diff --git a/shared/src/appleMain/kotlin/xyz/tyiu/satsprice/SystemCurrencies.apple.kt b/shared/src/appleMain/kotlin/xyz/tyiu/satsprice/SystemCurrencies.apple.kt index ef123ec..f1183d9 100644 --- a/shared/src/appleMain/kotlin/xyz/tyiu/satsprice/SystemCurrencies.apple.kt +++ b/shared/src/appleMain/kotlin/xyz/tyiu/satsprice/SystemCurrencies.apple.kt @@ -37,3 +37,5 @@ actual fun currencyDecimalDigits(code: String): Int { actual fun regionDisplayName(regionCode: String): String? = NSLocale.currentLocale.localizedStringForCountryCode(regionCode) + +actual fun supportsFlagEmoji(): Boolean = true diff --git a/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/CurrencyFlag.kt b/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/CurrencyFlag.kt index 0d21955..e1c5ff4 100644 --- a/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/CurrencyFlag.kt +++ b/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/CurrencyFlag.kt @@ -34,10 +34,13 @@ internal fun issuingCountryCodes(code: String): List { /** * A country flag emoji for [code], or null when there's no flag to show — either because [code] - * isn't tied to any country, or because it's shared by more than [MAX_FLAGS_PER_CURRENCY] - * countries, which would be too visually noisy to show side by side. + * isn't tied to any country, because it's shared by more than [MAX_FLAGS_PER_CURRENCY] countries + * (too visually noisy to show side by side), or because [supportsFlagEmoji] says the platform + * can't render one anyway (Compose for Web's canvas-based text renderer has no color-emoji font + * to fall back to, so a flag's regional-indicator codepoints draw as empty boxes there). */ fun currencyFlagEmoji(code: String): String? { + if (!supportsFlagEmoji()) return null val regionCodes = issuingCountryCodes(code) if (regionCodes.isEmpty() || regionCodes.size > MAX_FLAGS_PER_CURRENCY) return null return regionCodes.joinToString(" ") { regionFlagEmoji(it) } diff --git a/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/SystemCurrencies.kt b/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/SystemCurrencies.kt index 2ad23c5..312da89 100644 --- a/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/SystemCurrencies.kt +++ b/shared/src/commonMain/kotlin/xyz/tyiu/satsprice/SystemCurrencies.kt @@ -45,3 +45,10 @@ expect fun localeCurrencyCode(): String? /** The conventional number of decimal places for [code] (e.g. 2 for USD, 0 for JPY, 3 for BHD). */ expect fun currencyDecimalDigits(code: String): Int + +/** + * Whether the current platform's text renderer can draw flag emoji. False on Compose for Web + * (js/wasmJs): its canvas-based Skia renderer has no bundled or system color-emoji font to draw a + * flag's regional-indicator codepoints with, so they'd otherwise render as empty boxes. + */ +expect fun supportsFlagEmoji(): Boolean diff --git a/shared/src/jvmMain/kotlin/xyz/tyiu/satsprice/SystemCurrencies.jvm.kt b/shared/src/jvmMain/kotlin/xyz/tyiu/satsprice/SystemCurrencies.jvm.kt index 3a19597..6f0878e 100644 --- a/shared/src/jvmMain/kotlin/xyz/tyiu/satsprice/SystemCurrencies.jvm.kt +++ b/shared/src/jvmMain/kotlin/xyz/tyiu/satsprice/SystemCurrencies.jvm.kt @@ -43,3 +43,5 @@ actual fun regionDisplayName(regionCode: String): String? { if (regionCode !in Locale.getISOCountries()) return null return Locale("", regionCode).getDisplayCountry(Locale.getDefault()) } + +actual fun supportsFlagEmoji(): Boolean = true diff --git a/shared/src/webMain/kotlin/xyz/tyiu/satsprice/SystemCurrencies.web.kt b/shared/src/webMain/kotlin/xyz/tyiu/satsprice/SystemCurrencies.web.kt index 5b5cdc6..092b331 100644 --- a/shared/src/webMain/kotlin/xyz/tyiu/satsprice/SystemCurrencies.web.kt +++ b/shared/src/webMain/kotlin/xyz/tyiu/satsprice/SystemCurrencies.web.kt @@ -53,3 +53,8 @@ private fun jsRegionDisplayName(regionCode: String): String = js( actual fun regionDisplayName(regionCode: String): String? = jsRegionDisplayName(regionCode).takeIf { it != regionCode } + +// Compose for Web renders everything through Skia onto a rather than through the +// browser's own text stack, so it can't fall back to the browser/OS's color-emoji font the way +// native targets can — a flag's regional-indicator codepoints draw as empty boxes instead. +actual fun supportsFlagEmoji(): Boolean = false