bslib
bslib copied to clipboard
Add css for pandoc syntax highlighting themes
If your html contains code, you're likely to want to match the syntax highlighting colours with the them as a whole. So I think it makes sense for bootstraplib to provide some tools to turn pandoc themes into css. Could building from something like this:
library(jsonlite)
library(purrr)
library(dplyr)
library(farver)
library(ggplot2)
# From https://github.com/jgm/skylighting/blob/a1d02a0db6260c73aaf04aae2e6e18b569caacdc/skylighting-core/src/Skylighting/Format/HTML.hs#L117-L147
abbr <- c(
"Keyword" = "kw",
"DataType" = "dt",
"DecVal" = "dv",
"BaseN" = "bn",
"Float" = "fl",
"Char" = "ch",
"String" = "st",
"Comment" = "co",
"Other" = "ot",
"Alert" = "al",
"Function" = "fu",
"RegionMarker" = "re",
"Error" = "er",
"Constant" = "cn",
"SpecialChar" = "sc",
"VerbatimString" = "vs",
"SpecialString" = "ss",
"Import" = "im",
"Documentation" = "do",
"Annotation" = "an",
"CommentVar" = "cv",
"Variable" = "va",
"ControlFlow" = "cf",
"Operator" = "op",
"BuiltIn" = "bu",
"Extension" = "ex",
"Preprocessor" = "pp",
"Attribute" = "at",
"Information" = "in",
"Warning" = "wa",
"Normal" = ""
)
theme <- jsonlite::read_json("https://raw.githubusercontent.com/rstudio/distill/master/inst/rmarkdown/templates/distill_article/resources/arrow.theme")
as_row <- function(x) {
x %>%
modify_if(is.null, ~ NA) %>%
as_tibble()
}
styles_df <- theme$`text-styles` %>%
purrr::map_df(as_row, .id = "name") %>%
rename(color = `text-color`, background = `background-color`) %>%
mutate(abbr = unname(abbr[name]))
# From https://accessible-colors.com
rel_l <- function(x) {
scale <- function(x) {
ifelse(x <= 0.03928, x / 12.92, ((x + 0.055) / 1.055)^2.4)
}
rgb <- farver::decode_colour(x) / 255
0.2126 * scale(rgb[, 1]) + 0.7152 * scale(rgb[, 2]) + 0.0722 * scale(rgb[, 3])
}
contrast_ratio <- function(x, y) {
x_l <- rel_l(x)
y_l <- rel_l(y)
(pmax(x_l, y_l) + 0.05) / (pmin(x_l, y_l) + 0.05)
}
styles_df %>% mutate(contrast = contrast_ratio(color, "#fefefe"))
Thanks, we'll have to think about how this should interplay with html_document()
's existing highlight
param (it probably makes sense to set highlight = NULL
if theme
is a bs_theme()
and providing our own highlighting?)
https://github.com/rstudio/rmarkdown/blob/2787b5038c1080fe4dbfabcf73f76277d86df8e0/inst/rmd/h/default.html#L65-L102
@cpsievert I don't think setting it to NULL
is correct because that would suppress syntax highlighting altogether (rather than just changing the visual appearance)
@hadley is there any reason why you've omitted this code to translate the data frame to css (which was in this gist you shared with me previously)?
# https://github.com/jgm/skylighting/blob/a1d02a0db6260c73aaf04aae2e6e18b569caacdc/skylighting-core/src/Skylighting/Format/HTML.hs#L203
to_css <- function(abbr, color, background, bold, italic, underline, ...) {
attr <- c(
if (!is.na(color)) paste0("color:", color),
if (!is.na(background)) paste0("background-color:", background),
if (bold) "font-weight: bold",
if (italic) "font-style: italic",
if (underline) "text-decoration: underline"
)
paste0("code span.", abbr, " {", paste0(attr, collapse = "; "), "}")
}
styles_df %>% pmap_chr(to_css) %>% writeLines()
code span.ot {color:#ff4000}
code span.at {}
code span.ss {color:#008080}
code span.an {color:#008000}
code span.st {color:#036a07}
code span.dv {color:#0000cd}
code span.fl {color:#0000cd}
code span.cf {color:#0000ff}
code span.op {color:#687687}
code span.er {color:#ff0000; font-weight: bold}
code span.al {color:#ff0000}
code span.va {}
code span.bu {}
code span.ex {}
code span.pp {color:#ff4000}
code span.in {color:#008000}
code span.vs {color:#008080}
code span.wa {color:#008000; font-weight: bold}
code span.do {color:#008000}
code span.im {}
code span.ch {color:#008080}
code span.co {color:#4c886b}
code span.cv {color:#008000}
code span.cn {color:#585cf6}
code span.sc {color:#008080}
code span.kw {color:#0000ff}
No, I just forgot about it. BTW another possible home for this is the pandoc package that @cderv might eventually build.
I am still not yet enough familiar with bslib, but CSS files derived from Pandoc highlighting theme would indeed fit in the future mentioned package. Or a least a function to build them.
Ok great, I'm currently thinking this wouldn't be {bslib}
's job anyway, but rmarkdown (or this pandoc package) would be responsible for translating a relevant bs_theme()
into a pandoc highlighting theme via bs_get_variables()
. For Bootstrap 4, it could do something like this (we'll have to do something slightly different for Bootstrap 3):
# Translate Bootstrap Sass semantics to pandoc's syntax highlighting
# This translation is inspired by distill's arrow.theme semantics
# https://raw.githubusercontent.com/rstudio/distill/master/inst/rmarkdown/templates/distill_article/resources/arrow.theme
bs_theme_pandoc vars <- bs_get_variables(
theme, c("bg", "fg", "primary", "success", "warning", "danger")
)
vars <- setNames(htmltools::parseCssColors(vars), names(vars))
gray_pal <- scales::colour_ramp(vars[c("bg", "fg")])
jsonlite::fromJSON(glue::glue(
.open = "{{",
.close = "}}",
'{
"text-color": null,
"background-color": null,
"line-number-color": "{{gray_pal(2/3)}}",
"line-number-background-color": null,
"text-styles": {
"Other": {
"text-color": "{{vars[["primary"]]}}",
"background-color": null,
"bold": false,
"italic": false,
"underline": false
},
"Attribute": {
"text-color": "{{vars[["success"]]}}",
"background-color": null,
"bold": false,
"italic": false,
"underline": false
},
"SpecialString": {
"text-color": "{{vars[["success"]]}}",
"background-color": null,
"bold": false,
"italic": false,
"underline": false
},
"Annotation": {
"text-color": "{{gray_pal(0.37)}}",
"background-color": null,
"bold": false,
"italic": false,
"underline": false
},
"Function": {
"text-color": "{{scales::colour_ramp(vars[["fg"]], vars[["primary"]])(0.2)}}",
"background-color": null,
"bold": false,
"italic": false,
"underline": false
},
"String": {
"text-color": "{{vars[["success"]]}}",
"background-color": null,
"bold": false,
"italic": false,
"underline": false
},
"ControlFlow": {
"text-color": "{{vars[["primary"]]}}",
"background-color": null,
"bold": false,
"italic": false,
"underline": false
},
"Operator": {
"text-color": "{{gray_pal(0.37)}}",
"background-color": null,
"bold": false,
"italic": false,
"underline": false
},
"Error": {
"text-color": "{{vars[["danger"]]}}",
"background-color": null,
"bold": false,
"italic": false,
"underline": false
},
"BaseN": {
"text-color": "{{vars[["danger"]]}}",
"background-color": null,
"bold": false,
"italic": false,
"underline": false
},
"Alert": {
"text-color": "{{vars[["danger"]]}}",
"background-color": null,
"bold": false,
"italic": false,
"underline": false
},
"Variable": {
"text-color": "{{gray_pal(0.06)}}",
"background-color": null,
"bold": false,
"italic": false,
"underline": false
},
"BuiltIn": {
"text-color": null,
"background-color": null,
"bold": false,
"italic": false,
"underline": false
},
"Extension": {
"text-color": null,
"background-color": null,
"bold": false,
"italic": false,
"underline": false
},
"Preprocessor": {
"text-color": "{{vars[["danger"]]}}",
"background-color": null,
"bold": false,
"italic": false,
"underline": false
},
"Information": {
"text-color": "{{gray_pal(0.37)}}",
"background-color": null,
"bold": false,
"italic": false,
"underline": false
},
"VerbatimString": {
"text-color": "{{vars[["success"]]}}",
"background-color": null,
"bold": false,
"italic": false,
"underline": false
},
"Warning": {
"text-color": "{{gray_pal(0.37)}}",
"background-color": null,
"bold": false,
"italic": true,
"underline": false
},
"Documentation": {
"text-color": "{{gray_pal(0.37)}}",
"background-color": null,
"bold": false,
"italic": true,
"underline": false
},
"Import": {
"text-color": null,
"background-color": null,
"bold": false,
"italic": false,
"underline": false
},
"Char": {
"text-color": "{{vars[["success"]]}}",
"background-color": null,
"bold": false,
"italic": false,
"underline": false
},
"DataType": {
"text-color": "{{vars[["danger"]]}}",
"background-color": null,
"bold": false,
"italic": false,
"underline": false
},
"Float": {
"text-color": "{{vars[["danger"]]}}",
"background-color": null,
"bold": false,
"italic": false,
"underline": false
},
"Comment": {
"text-color": "{{gray_pal(0.37)}}",
"background-color": null,
"bold": false,
"italic": false,
"underline": false
},
"CommentVar": {
"text-color": "{{gray_pal(0.37)}}",
"background-color": null,
"bold": false,
"italic": true,
"underline": false
},
"Constant": {
"text-color": "{{vars[["warning"]]}}",
"background-color": null,
"bold": false,
"italic": false,
"underline": false
},
"SpecialChar": {
"text-color": "{{vars[["success"]]}}",
"background-color": null,
"bold": false,
"italic": false,
"underline": false
},
"DecVal": {
"text-color": "{{vars[["danger"]]}}",
"background-color": null,
"bold": false,
"italic": false,
"underline": false
},
"Keyword": {
"text-color": "{{vars[["primary"]]}}",
"background-color": null,
"bold": false,
"italic": false,
"underline": false
}
}
}'
))
}
So the idea would be to create a highlighting theme to pass to Pandoc from a selected bs_theme()
?
I find the idea interesting.
But I am now not so sure I understood correctly in the first place now... I thought this issue was about turning Pandoc's highlighting theme (like our custom arrow.theme or rstudio.theme) into css to be used with {bslib} - Did I misunderstand from the beginning or do you think this is better the other way around ?
Yea, after looking into it more, I don't think it's right for bslib to be including css, for a couple reasons:
- I'm fairly sure, when opt-ing into pandoc syntax highlighting, a theme must be specified and pandoc always includes the relevant CSS. Thus, we'd have to overwrite those rules without knowing whether or not you're even using syntax highlighting (making things unnecessarily bloated and convoluted)
- Pandoc could decide to change the HTML markup structure at some point.
I'm going to do this in pkgdown because I need to bundle some syntax highlighting choices: https://github.com/r-lib/pkgdown/pull/1841