feat(audio-rooms): font-tag parser + system-font typography (T3 #1)
Closes the deferred font tag from the Tier-3 plan. Wire-up:
* quartz: FontTag(family, optionalUrl) parser/assembler with
blank-rejection on family + blank-URL-becomes-null normalisation.
* MeetingSpaceEvent.font(): FontTag? accessor.
* TagArrayBuilder.font(family, url) DSL.
* RoomTheme gains fontFamily / fontUrl fields.
* AudioRoomThemedScope maps `family` to a Compose FontFamily for
the four CSS-style generic-family names ("sans-serif", "serif",
"monospace", "cursive") — each maps to the corresponding system
fallback. The whole Material3 typography is rebuilt with that
family so headlines / body / labels all swap together.
URL-based font loading (RoomTheme.fontUrl) is the natural follow-up:
fetch + cache via OkHttp, then build a FontFamily from a local
file. Until then, an unknown family is silently a no-op — the room
renders in the platform default rather than crashing or fetching
on the UI thread.
Tests:
* FontTagTest — 9 cases covering family-only, family+URL,
blank rejection, missing family, wrong tag name, blank-URL
normalisation, assembler shapes (with + without URL), and
the assembler's blank-family rejection.
* RoomThemeTest gains 3 cases for font projection (family-only /
family+URL / empty event).
This commit is contained in:
+28
-1
@@ -43,11 +43,35 @@ data class RoomTheme(
|
||||
val primaryArgb: Long?,
|
||||
val backgroundImageUrl: String?,
|
||||
val backgroundMode: BackgroundMode,
|
||||
/**
|
||||
* Suggested font family from the kind-30312 `["f", family,
|
||||
* optionalUrl]` tag. Renderers map this to a platform
|
||||
* FontFamily — the system-font shorthand list ("sans-serif",
|
||||
* "serif", "monospace", "cursive") is honoured directly;
|
||||
* other names fall back to the platform default unless the
|
||||
* renderer also fetches [fontUrl].
|
||||
*/
|
||||
val fontFamily: String?,
|
||||
/**
|
||||
* Optional URL to a font file (e.g. WOFF2). The protocol
|
||||
* permits async loading; clients without a font-fetch loader
|
||||
* stay on [fontFamily] alone (or default typography on miss).
|
||||
*/
|
||||
val fontUrl: String?,
|
||||
) {
|
||||
enum class BackgroundMode { COVER, TILE }
|
||||
|
||||
companion object {
|
||||
val Empty = RoomTheme(null, null, null, null, BackgroundMode.COVER)
|
||||
val Empty =
|
||||
RoomTheme(
|
||||
backgroundArgb = null,
|
||||
textArgb = null,
|
||||
primaryArgb = null,
|
||||
backgroundImageUrl = null,
|
||||
backgroundMode = BackgroundMode.COVER,
|
||||
fontFamily = null,
|
||||
fontUrl = null,
|
||||
)
|
||||
|
||||
/**
|
||||
* Project the theme tags off a kind-30312 event. Returns
|
||||
@@ -57,6 +81,7 @@ data class RoomTheme(
|
||||
fun from(event: MeetingSpaceEvent): RoomTheme {
|
||||
val colors = event.colors()
|
||||
val bg = event.background()
|
||||
val font = event.font()
|
||||
|
||||
fun pickHex(target: ColorTag.Target): Long? =
|
||||
colors
|
||||
@@ -74,6 +99,8 @@ data class RoomTheme(
|
||||
BackgroundTag.Mode.TILE -> BackgroundMode.TILE
|
||||
else -> BackgroundMode.COVER
|
||||
},
|
||||
fontFamily = font?.family,
|
||||
fontUrl = font?.url,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+24
@@ -128,4 +128,28 @@ class RoomThemeTest {
|
||||
assertEquals(0xFF000000L, RoomTheme.hexToOpaqueArgb("000000"))
|
||||
assertEquals(0xFFFFFFFFL, RoomTheme.hexToOpaqueArgb("FFFFFF"))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fontFamilyOnly() {
|
||||
val theme = RoomTheme.from(event(listOf(arrayOf("f", "Inter"))))
|
||||
assertEquals("Inter", theme.fontFamily)
|
||||
assertNull(theme.fontUrl)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun fontFamilyAndUrl() {
|
||||
val theme =
|
||||
RoomTheme.from(
|
||||
event(listOf(arrayOf("f", "Inter", "https://fonts.example/inter.woff2"))),
|
||||
)
|
||||
assertEquals("Inter", theme.fontFamily)
|
||||
assertEquals("https://fonts.example/inter.woff2", theme.fontUrl)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun emptyFontDefaultsBothNull() {
|
||||
val theme = RoomTheme.from(event(emptyList()))
|
||||
assertNull(theme.fontFamily)
|
||||
assertNull(theme.fontUrl)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user