|
12 | 12 | from .._versions import bootstrap |
13 | 13 | from ._theme import Theme |
14 | 14 | from ._theme_presets import ShinyThemePreset, shiny_theme_presets |
| 15 | +from .css import CssUnit, as_css_unit |
15 | 16 |
|
16 | 17 |
|
17 | 18 | class ThemeBrandUnmappedFieldError(ValueError): |
@@ -229,6 +230,11 @@ def __init__( |
229 | 230 | if typ_field_key in typography_map[typ_field]: |
230 | 231 | typo_sass_var = typography_map[typ_field][typ_field_key] |
231 | 232 |
|
| 233 | + if typ_field == "base" and typ_field_key == "size": |
| 234 | + typ_field_value = str( |
| 235 | + maybe_convert_font_size_to_rem(typ_field_value) |
| 236 | + ) |
| 237 | + |
232 | 238 | sass_vars_typography[typo_sass_var] = typ_field_value |
233 | 239 | elif raise_for_unmapped_vars: |
234 | 240 | raise ThemeBrandUnmappedFieldError( |
@@ -317,3 +323,65 @@ def _html_dependencies(self) -> list[HTMLDependency]: |
317 | 323 | def sanitize_sass_var_name(x: str) -> str: |
318 | 324 | x = re.sub(r"""['"]""", "", x) |
319 | 325 | return re.sub(r"[^a-zA-Z0-9_-]+", "-", x) |
| 326 | + |
| 327 | + |
| 328 | +def maybe_convert_font_size_to_rem(x: str) -> CssUnit: |
| 329 | + """ |
| 330 | + Convert a font size to rem |
| 331 | +
|
| 332 | + Bootstrap expects base font size to be in `rem`. This function converts `em`, `%`, |
| 333 | + `px`, `pt` to `rem`: |
| 334 | +
|
| 335 | + 1. `em` is directly replace with `rem`. |
| 336 | + 2. `1%` is `0.01rem`, e.g. `90%` becomes `0.9rem`. |
| 337 | + 3. `16px` is `1rem`, e.g. `18px` becomes `1.125rem`. |
| 338 | + 4. `12pt` is `1rem`. |
| 339 | + 5. `0.1666in` is `1rem`. |
| 340 | + 6. `4.234cm` is `1rem`. |
| 341 | + 7. `42.3mm` is `1rem`. |
| 342 | + """ |
| 343 | + x_og = f"{x}" |
| 344 | + x = as_css_unit(x) |
| 345 | + |
| 346 | + value, unit = split_css_value_and_unit(x) |
| 347 | + |
| 348 | + if unit == "rem": |
| 349 | + return x |
| 350 | + |
| 351 | + if unit == "em": |
| 352 | + return as_css_unit(f"{value}rem") |
| 353 | + |
| 354 | + scale = { |
| 355 | + "%": 100, |
| 356 | + "px": 16, |
| 357 | + "pt": 12, |
| 358 | + "in": 96 / 16, # 96 px/inch |
| 359 | + "cm": 96 / 16 * 2.54, # inch -> cm |
| 360 | + "mm": 16 / 96 * 25.4, # cm -> mm |
| 361 | + } |
| 362 | + |
| 363 | + if unit in scale: |
| 364 | + return as_css_unit(f"{float(value) / scale[unit]}rem") |
| 365 | + |
| 366 | + raise ValueError( |
| 367 | + f"Shiny does not support brand.yml font sizes in {unit} units ({x_og!r})" |
| 368 | + ) |
| 369 | + |
| 370 | + |
| 371 | +def split_css_value_and_unit(x: str) -> tuple[str, str]: |
| 372 | + digit_chars = [".", *[str(s) for s in range(10)]] |
| 373 | + |
| 374 | + value = "" |
| 375 | + unit = "" |
| 376 | + in_unit = False |
| 377 | + for chr in x: |
| 378 | + if chr in digit_chars: |
| 379 | + if not in_unit: |
| 380 | + value += chr |
| 381 | + else: |
| 382 | + in_unit = True |
| 383 | + |
| 384 | + if in_unit: |
| 385 | + unit += chr |
| 386 | + |
| 387 | + return value.strip(), unit.strip() |
0 commit comments