Skip to content

Commit fc66b21

Browse files
committed
feat: Convert typography.base.size to rem for Bootstrap
Otherwise Bootstrap does calculations with incompatible units
1 parent 96a9b95 commit fc66b21

File tree

1 file changed

+68
-0
lines changed

1 file changed

+68
-0
lines changed

shiny/ui/_theme_brand.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from .._versions import bootstrap
1313
from ._theme import Theme
1414
from ._theme_presets import ShinyThemePreset, shiny_theme_presets
15+
from .css import CssUnit, as_css_unit
1516

1617

1718
class ThemeBrandUnmappedFieldError(ValueError):
@@ -229,6 +230,11 @@ def __init__(
229230
if typ_field_key in typography_map[typ_field]:
230231
typo_sass_var = typography_map[typ_field][typ_field_key]
231232

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+
232238
sass_vars_typography[typo_sass_var] = typ_field_value
233239
elif raise_for_unmapped_vars:
234240
raise ThemeBrandUnmappedFieldError(
@@ -317,3 +323,65 @@ def _html_dependencies(self) -> list[HTMLDependency]:
317323
def sanitize_sass_var_name(x: str) -> str:
318324
x = re.sub(r"""['"]""", "", x)
319325
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

Comments
 (0)