bslib icon indicating copy to clipboard operation
bslib copied to clipboard

Allow adding multiple local font-styles with bs_theme & font_face

Open markschat opened this issue 2 years ago • 5 comments

Hi, either I can´t figure it out, or it is not possible as of now. I´m not able to pass more than one style from a font-family to bs_theme (e.g normal, bold, italic from 'Open Sans').

Reprex

library(shiny)
library(bslib)
library(htmltools)

# Borrow fonts from bslib ----
addResourcePath("fonts", system.file("fonts", package = "bslib"))

# Define font collection ----
nunito <- font_collection(
  font_face("Nunito", weight = 400, style = "normal", src = "url('../fonts/XRXV3I6Li01BKof4MQ.woff')"),
  font_face("Nunito", weight = 700, style = "normal", src = "url('../fonts/XRXW3I6Li01BKofAjsOkZQ.woff')")
)

ui <- fluidPage(
  theme = bs_theme(base_font = nunito),
  tags$p("Normal Text"),
  tags$b("Bold Text (seems bold, but is thickened by browser I suppose)"),
)
shinyApp(ui, \(...){})

As I inspect this app via the browser dev-tools, I can see this CSS declaration (../_Nunito-0.4.0/font.css):

@font-face {
  font-family: Nunito;
  src: url('../fonts/XRXV3I6Li01BKof4MQ.woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

My font is included 🎉 but only the first style (weight 400) -> bold/700 is missing. Here´s what I would expect in the file:

@font-face {
  font-family: Nunito;
  src: url('../fonts/XRXV3I6Li01BKof4MQ.woff');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: Nunito;
  src: url('../fonts/XRXW3I6Li01BKofAjsOkZQ.woff');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

The browser still produces a thickened text (reprex), but I think this is some browser-magic and not the months of work the font designer put into developing the bold style as it is missing in the font-face declaration.

markschat avatar Oct 12 '21 15:10 markschat

I think you can workaround this bug by replacing nunito with font_face("Nunito", weight = c(400, 700))?

cpsievert avatar Oct 12 '21 15:10 cpsievert

Oh actually, I misunderstood what you're trying to do, I'm not sure if that workaround works if you need the 2 different weights to point to 2 different files.

Any reason why you can't use font_google("Nunito", wght = c(400, 700)) instead?

cpsievert avatar Oct 12 '21 16:10 cpsievert

Thanks for your reply @cpsievert! In my case I don´t use the (convenient) font_google function for data privacy reasons + a blocking firewall. For others a use case may be using some special non-google font

After a deep dive into CSS, font_face etc. my current status (for woff/woff2-font files) is the following:

Traditional approach

  • Professional fonts are usually served in multiple files (each file representing a carefully crafted variation like semi-bold)
  • Each font variation can be imported with an @font_face declaration (like in the intoducing reprex)
  • This approach has currently a wide browser support

Variable Fonts

  • A relatively new font format is variable fonts
  • Multiple font variations are included into a single file
  • Less browser support than the traditional approach, as it is a new format.

Still, many variable fonts still distinguish between normal and italic putting both into separate files 😟. Open Sans (as a variable font) for example is split into this two files:

  • OpenSans-VariableFont_wdth,wght.ttf
  • OpenSans-Italic-VariableFont_wdth,wght.ttf

Maybe there is a third approach putting all font variations into a single file and declaring them via @font_face that I´m currently not aware of.

I would be very glad to somehow have the possibility to use custom fonts (contained in multiple files) within shiny 😀

markschat avatar Oct 12 '21 19:10 markschat

I would be very glad to somehow have the possibility to use custom fonts (contained in multiple files) within shiny

I hope we'll be able fix the core issue with font_face() here (the CSS is being de-duplicated when it in fact shouldn't be), but given that you need this level of control, there isn't much of any benefit to using font_face() over writing the CSS by hand, like this:

addResourcePath("fonts" = "../fonts")
ui <- fluidPage(
  theme = bs_theme(base_font = "Nunito") %>%
     bs_add_rules(
       "@font-face {
           font-family: Nunito;
           src: url('fonts/XRXV3I6Li01BKof4MQ.woff');
           font-weight: 400;
           font-style: normal;
           font-display: swap;
        }"
     ),
  tags$p("Normal Text"),
  tags$b("Bold Text (seems bold, but is thickened by browser I suppose)"),
)
shinyApp(ui, \(...){})

cpsievert avatar Oct 12 '21 22:10 cpsievert

I see, that seems to be the best solution for now! Thank´s for your suggestion!

markschat avatar Oct 13 '21 06:10 markschat