Introduce partner and funder logos
Change-Id: I71e00aaa51c2dffe74335c7c4f170e8d507356d6
diff --git a/DESCRIPTION b/DESCRIPTION
index 9e6c402..c4d244c 100644
--- a/DESCRIPTION
+++ b/DESCRIPTION
@@ -24,7 +24,9 @@
Imports:
rmarkdown (>= 1.7)
Suggests:
+ base64enc,
bslib,
+ qrcode,
testthat (>= 3.0.0),
withr (>= 2.4.2),
xfun (>= 0.21),
diff --git a/NAMESPACE b/NAMESPACE
index 86e0d69..da90358 100644
--- a/NAMESPACE
+++ b/NAMESPACE
@@ -1,5 +1,6 @@
# Generated by roxygen2: do not edit by hand
+export(qrlink)
export(revealjs_presentation)
export(slide_timing)
import(rmarkdown)
diff --git a/NEWS.md b/NEWS.md
index a5593f8..e4e3de4 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -1,5 +1,7 @@
# revealjs.ids (development version)
+- Title slide branding (ported from posterdown.ids): three new optional yaml entries -- `website` places a linked QR code (generated with the qrcode package) in the top left of the title slide; `partner_logo` shows a second institution's logo next to the IDS logo in the top right of every slide (for talks by authors from two institutions); `funder_logo` puts a funder logo in the bottom left of the title slide, opposite the Leibniz Gemeinschaft membership logo. Local logo files are embedded as data URIs, so they need not be shipped; a linked QR code anywhere on a slide can be produced with the new `qrlink()` function in inline R code. The navigation chrome (arrows, slide number, menu button) is now hidden on the title slide.
+
- New `df_print` option (default `"kable"`): data frames are now printed as theme-styled HTML tables by default, so raw results on slides look reasonable without extra code. Use `df_print: default` for plain text output (or `tibble` / `paged` as in `rmarkdown::html_document`); `"kable"` prints all rows, so show only `head(x)` on slides with many-rowed results.
- New transcript view, toggled with `r` (or, on touch devices, with a list button next to the speaker notes button): a single scrollable page containing every slide's content and speaker notes. Text-to-speech ("read aloud") browser extensions cannot walk a reveal.js deck (hidden slides are not part of the visible page text), but they can read the transcript from start to finish -- e.g. to have the whole talk including notes read aloud while practicing.
diff --git a/R/revealjs_presentation.R b/R/revealjs_presentation.R
index 68d0733..05c8131 100644
--- a/R/revealjs_presentation.R
+++ b/R/revealjs_presentation.R
@@ -152,6 +152,28 @@
#' toggleNotesButton: true
#' toggleChalkboardButton: true
#' ```
+#' ## Title slide: QR code and additional logos
+#'
+#' Three optional yaml entries personalise the title slide for external
+#' audiences (ported from \pkg{posterdown.ids}):
+#'
+#' * `website: <url>` -- a linked QR code pointing to the URL is placed in
+#' the top left of the title slide. Uses the \pkg{qrcode} package.
+#' * `partner_logo: <path-or-url>` -- when a presentation is given by
+#' authors from two institutions, the partner institution's logo is shown
+#' next to the IDS logo, in the top right of every slide.
+#' * `funder_logo: <path-or-url>` -- a funder logo (e.g. "Text+") is placed
+#' in the bottom left of the title slide, opposite the Leibniz
+#' Gemeinschaft membership logo. It is lifted above the footer band on
+#' print (pdf) pages.
+#'
+#' Local logo files (svg, png, jpg, gif, webp) are embedded in the html as
+#' data URIs, so they do not need to be shipped with the presentation; http
+#' URLs are used as-is. A QR code anywhere else on a slide can be produced
+#' with [qrlink()] from inline R code. The navigation chrome (arrows, slide
+#' number, menu button) is hidden on the title slide for a bare first
+#' impression.
+#'
#' ## Bibliography and citations
#'
#' Documents with a `bibliography:` in their YAML header are formatted with
@@ -429,6 +451,21 @@
# highlight
args <- c(args, pandoc_highlight_args(highlight, default = "pygments"))
+ # website entry: place a linked QR code (inline SVG) in the top left of
+ # the title slide
+ if (!is.null(metadata$website)) {
+ args <- c(args, pandoc_variable_arg("website-qr", qrlink(metadata$website)))
+ }
+
+ # partner / funder institution logos: local files are inlined as data
+ # URIs so they resolve correctly in CSS and stay embedded in the output
+ if (!is.null(metadata$partner_logo)) {
+ args <- c(args, pandoc_variable_arg("partner-logo-src", logo_uri(metadata$partner_logo)))
+ }
+ if (!is.null(metadata$funder_logo)) {
+ args <- c(args, pandoc_variable_arg("funder-logo-src", logo_uri(metadata$funder_logo)))
+ }
+
# default to the bundled IDS citation style unless the document
# specifies a style of its own
if (is.null(metadata$csl)) {
diff --git a/R/utils.R b/R/utils.R
index 0c13ae5..4f2c21f 100644
--- a/R/utils.R
+++ b/R/utils.R
@@ -46,3 +46,76 @@
}
pandoc_variable_arg(option, value)
}
+
+# Resolve a logo image (yaml entries `partner_logo` / `funder_logo`) to a URL
+# usable inside CSS: http(s) and data URLs pass through unchanged, local
+# files are inlined as base64 data URIs. Inlining is required: CSS background
+# URLs would resolve relative to the theme stylesheet, and document-relative
+# files would be left behind when the rendered html is deployed elsewhere.
+logo_uri <- function(path) {
+ if (grepl("^(https?|data):", path)) {
+ return(path)
+ }
+ if (!file.exists(path)) {
+ stop("Logo file not found: ", path, call. = FALSE)
+ }
+ ext <- tolower(tools::file_ext(path))
+ mime <- switch(ext,
+ svg = "image/svg+xml", png = "image/png", jpg = , jpeg = "image/jpeg",
+ gif = "image/gif", webp = "image/webp",
+ stop("Unsupported logo format '", ext, "' (use svg, png, jpg, gif or webp)",
+ call. = FALSE
+ )
+ )
+ if (!requireNamespace("base64enc", quietly = TRUE)) {
+ stop("Package \"base64enc\" needed to embed local logo files. Please install it.",
+ call. = FALSE
+ )
+ }
+ sprintf("data:%s;base64,%s", mime, base64enc::base64encode(path))
+}
+
+#' HTML code for a linked QR code
+#'
+#' Generates a QR code (inline SVG) linking to the given URL and returns the
+#' corresponding HTML code, so a QR code can be placed anywhere in a
+#' presentation with inline R code.
+#'
+#' The `website` YAML entry of [revealjs_presentation()] uses this to
+#' place a linked QR code in the top left of the title slide.
+#'
+#' @param url Target URL for the QR code
+#' @param logo Optional path to a logo image to place in the center of the QR
+#' code
+#' @param text Optional caption displayed below the QR code (alias:
+#' `caption`)
+#' @param caption Alias for `text`
+#' @return Character string with HTML code for a linked QR code
+#' @export
+qrlink <- function(url, logo = NULL, text = NULL, caption = text) {
+ if (!requireNamespace("qrcode", quietly = TRUE)) {
+ stop("Package \"qrcode\" needed to generate QR codes. Please install it.",
+ call. = FALSE
+ )
+ }
+ tmp <- tempfile(fileext = ".svg")
+ qrcode <- qrcode::qr_code(url, ecl = if (is.null(logo)) "L" else "H")
+ if (!is.null(logo)) {
+ qrcode <- qrcode::add_logo(qrcode, logo, ecl = "L")
+ }
+ qrcode::generate_svg(qrcode, tmp, show = FALSE)
+
+ cap_html <- if (!is.null(caption) && nchar(trimws(caption)) > 0) {
+ sprintf('<span class="qrcode-caption">%s</span>', caption)
+ } else {
+ ""
+ }
+
+ svg <- gsub("\r?\n|\r", "", readChar(tmp, 1e7))
+ qr <- sprintf('<a class="qrcode" href="%s">%s</a>', url, svg)
+ if (nchar(cap_html) > 0) {
+ paste0('<div class="qrcode-container">', qr, cap_html, "</div>")
+ } else {
+ qr
+ }
+}
diff --git a/README.Rmd b/README.Rmd
index 6925f7e..39d0246 100644
--- a/README.Rmd
+++ b/README.Rmd
@@ -11,7 +11,7 @@
## Screenshots
-Generated from [examples/ids.Rmd](./examples/ids.Rmd), see live demo [here](http://10.0.1.45/slides/2023-10-08-Revealjs-demo/ids.html).
+Generated from [examples/ids.Rmd](./examples/ids.Rmd), see live demo [here](http://10.0.1.45/slides/2023-10-08-Revealjs-demo/ids.html). A small starting-point deck is [examples/minimal.Rmd](./examples/minimal.Rmd).

@@ -255,6 +255,26 @@
center: true
```
+## Title Slide: QR Code and Logos
+
+Three optional yaml entries brand the title slide for external audiences:
+
+``` yaml
+title: "Habits"
+website: "https://korap.ids-mannheim.de/gerrit/IDS-Mannheim/revealjs"
+partner_logo: "dnb-logo.svg"
+funder_logo: "textplus-logo.svg"
+output: revealjs.ids::revealjs_presentation
+```
+
+- `website` places a linked QR code (pointing to the URL) in the top left of the title slide. Requires the `qrcode` package.
+
+- `partner_logo`, a path or URL to a second institution's logo, shows it next to the IDS logo in the top right of *every* slide -- for presentations given by authors from two institutions.
+
+- `funder_logo`, a path or URL to a funder logo, shows it in the bottom left of the title slide, opposite the Leibniz Gemeinschaft membership logo. It is lifted above the footer band on print (pdf) pages.
+
+Local logo files (svg, png, jpg, gif, webp) are embedded in the html as data URIs, so they do not need to be shipped with the presentation; http(s) URLs are used as-is. A linked QR code at any other place on a slide can be produced with `revealjs.ids::qrlink("https://...")` in inline R code. On the title slide the navigation chrome (arrows, slide number, menu button) is hidden, for a bare first impression.
+
## Slide Transitions
You can use the `transition` and `background_transition` options to specify the global default slide transition style:
diff --git a/README.md b/README.md
index a6cca11..52cabf4 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,9 @@
## Screenshots
Generated from [examples/ids.Rmd](./examples/ids.Rmd), see live demo
-[here](http://10.0.1.45/slides/2023-10-08-Revealjs-demo/ids.html).
+[here](http://10.0.1.45/slides/2023-10-08-Revealjs-demo/ids.html). A
+small starting-point deck is
+[examples/minimal.Rmd](./examples/minimal.Rmd).

@@ -327,6 +329,36 @@
center: true
```
+## Title Slide: QR Code and Logos
+
+Three optional yaml entries brand the title slide for external audiences:
+
+``` yaml
+title: "Habits"
+website: "https://korap.ids-mannheim.de/gerrit/IDS-Mannheim/revealjs"
+partner_logo: "dnb-logo.svg"
+funder_logo: "textplus-logo.svg"
+output: revealjs.ids::revealjs_presentation
+```
+
+- `website` places a linked QR code (pointing to the URL) in the top left
+ of the title slide. Requires the `qrcode` package.
+
+- `partner_logo`, a path or URL to a second institution’s logo, shows it
+ next to the IDS logo in the top right of *every* slide – for
+ presentations given by authors from two institutions.
+
+- `funder_logo`, a path or URL to a funder logo, shows it in the bottom
+ left of the title slide, opposite the Leibniz Gemeinschaft membership
+ logo. It is lifted above the footer band on print (pdf) pages.
+
+Local logo files (svg, png, jpg, gif, webp) are embedded in the html as
+data URIs, so they do not need to be shipped with the presentation;
+http(s) URLs are used as-is. A linked QR code at any other place on a
+slide can be produced with `revealjs.ids::qrlink("https://...")` in
+inline R code. On the title slide the navigation chrome (arrows, slide
+number, menu button) is hidden, for a bare first impression.
+
## Slide Transitions
You can use the `transition` and `background_transition` options to
diff --git a/examples/ids.Rmd b/examples/ids.Rmd
index 9114e18..d657904 100644
--- a/examples/ids.Rmd
+++ b/examples/ids.Rmd
@@ -6,6 +6,9 @@
- name: John Doe
institute: "IDS Mannheim"
date: "`r paste0('Mannheim, ', Sys.Date())`"
+website: "https://korap.ids-mannheim.de/gerrit/IDS-Mannheim/revealjs"
+partner_logo: "logos/clarin-logo.svg"
+funder_logo: "logos/text-plus-logo.svg"
output:
revealjs.ids::revealjs_presentation:
reveal_plugins:
diff --git a/examples/logos/clarin-logo.svg b/examples/logos/clarin-logo.svg
new file mode 100644
index 0000000..1a198b0
--- /dev/null
+++ b/examples/logos/clarin-logo.svg
@@ -0,0 +1,27 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+ viewBox="0 0 358 138"
+ shape-rendering="auto"
+ text-rendering="auto"
+ image-rendering="auto">
+
+ <style>
+ .logo-fill { fill:#00406f; }
+ </style>
+
+ <path class="logo-fill" d="M321.04,125h-1.17c0-31.75-26.35-57.5-58.85-57.5s-58.85,25.74-58.85,57.5h-1.17c0-32.38,26.87-58.64,60.02-58.64s60.02,26.25,60.02,58.64Z"/>
+ <path class="logo-fill" d="M248.43,130.64c-.48-.61-.96-1.22-1.41-1.84-.36-.49-.71-.98-1.06-1.49-18.53-26.86-11.26-63.3,16.23-81.4,27.47-18.09,64.75-11.01,83.29,15.82,2.11,3.05,3.92,6.29,5.41,9.68l-1.07.45c-12.85-29.2-47.49-42.69-77.38-30.14-3.37,1.41-6.59,3.13-9.63,5.13-26.95,17.76-34.06,53.49-15.88,79.82.34.49.69.98,1.04,1.46.45.6.91,1.21,1.39,1.81l-.92.7Z"/>
+ <path class="logo-fill" d="M259.95,101.78c-32.1.02-58.54-24.61-60.05-55.93l1.17-.05c1.52,31.72,29.08,56.23,61.55,54.74,32.47-1.49,57.55-28.41,56.03-60.13-.65-13.63-6.25-26.59-15.79-36.56l.85-.78c22.65,23.68,21.36,60.81-2.88,82.93-5.12,4.68-11.05,8.44-17.5,11.12-7.39,3.07-15.34,4.66-23.37,4.66Z"/>
+ <ellipse class="logo-fill" cx="319" cy="43.5" rx="10" ry="9.5"/>
+ <ellipse class="logo-fill" cx="218" cy="84.5" rx="10" ry="9.5"/>
+ <ellipse class="logo-fill" cx="237" cy="97.5" rx="10" ry="9.5"/>
+ <ellipse class="logo-fill" cx="260" cy="101.5" rx="10" ry="9.5"/>
+ <ellipse class="logo-fill" cx="283" cy="97.5" rx="10" ry="9.5"/>
+ <ellipse class="logo-fill" cx="302" cy="84.5" rx="10" ry="9.5"/>
+ <ellipse class="logo-fill" cx="315" cy="65.5" rx="10" ry="9.5"/>
+ <path class="logo-fill" d="M30.21,79.04c.02-.12.02-.25.02-.37,0-.23-.2-.47-.61-.7-.51-.28-1.05-.49-1.62-.64-.76-.21-1.53-.37-2.31-.48-.91-.13-1.82-.2-2.74-.2-.95,0-1.9.11-2.83.31-.96.19-1.87.53-2.72,1.01-.81.46-1.51,1.1-2.02,1.87-1.05,1.75-1.05,3.92,0,5.67.52.77,1.21,1.41,2.02,1.87.85.48,1.76.82,2.72,1.01.93.2,1.88.3,2.83.31.62-.02,1.23-.09,1.84-.2.85-.13,1.69-.32,2.52-.55.76-.21,1.51-.47,2.22-.79.64-.29.97-.6.97-.92,0-.12-.02-.25-.09-.35-.07-.1-.13-.2-.18-.31l.49-.31,4.63,6.89-.54.31c-.04-.12-.13-.23-.25-.29-.12-.05-.25-.07-.38-.07-.44.06-.86.19-1.26.37-.8.31-1.62.57-2.45.79-1.16.31-2.34.56-3.53.75-1.5.23-3.02.34-4.54.33-1.81,0-3.6-.23-5.35-.68-1.71-.43-3.32-1.16-4.76-2.15-1.4-.97-2.57-2.23-3.42-3.69-1.74-3.23-1.74-7.09,0-10.32.85-1.46,2.02-2.72,3.42-3.69,1.45-1,3.06-1.75,4.76-2.22,1.74-.49,3.54-.74,5.35-.75,1.56-.02,3.11.08,4.65.29,1.11.15,2.22.37,3.3.64.87.23,1.54.43,2.02.59.35.13.71.21,1.08.24.27,0,.52-.13.67-.35l.49.13-4.05,7.25-.45-.31c.03-.09.05-.19.07-.29Z"/>
+ <path class="logo-fill" d="M65.07,88.61c.16-.31.27-1.61.27-1.61h.66v8h-.66s-.09-.38-.27-.63c-.16-.11-.35-.17-.54-.37h-23.53v-.31c.44,0,.8-.1,1.12-.29.14-.13.21-.32-.12-.51v-20.46c.33-.19.26-.37.12-.51-.32-.19-1.12-.29-1.12-.29v-.64h9v.64s-.41.1-.73.29c-.14.13-.21.32-.27.51v16.57h15.53c.21-.13.41-.22.54-.39Z"/>
+ <path class="logo-fill" d="M77.35,93.36c.2.09.43.13.65.11v.53h-8s0-.52,0-.52c.25-.03.49-.13.7-.28.19-.21.34-.45.44-.72.06-.15.2-.54.43-1.18.23-.64.54-1.45.91-2.43.38-.98.8-2.06,1.26-3.26.46-1.2.93-2.41,1.39-3.65.46-1.24.92-2.45,1.37-3.63.45-1.18.85-2.24,1.2-3.17l.85-2.27c.22-.58.33-.89.33-.92,0-.15-.09-.25-.28-.31-.19-.06-.39-.1-.59-.13v-.52h12v.52c-.21.02-.41.06-.6.13-.15.06-.23.2-.23.44.12.39.27.77.44,1.14.29.7.67,1.61,1.13,2.73.46,1.12.99,2.39,1.59,3.8.6,1.41,1.22,2.86,1.87,4.33.65,1.47,1.27,2.91,1.87,4.31.6,1.4,1.15,2.65,1.64,3.76.15.38.36.73.62,1.05.17.18.42.27.67.26v.52h-9v-.53c.57-.03.86-.21.86-.53-.06-.2-.14-.4-.23-.59-.15-.37-.32-.77-.5-1.23-.18-.45-.35-.88-.52-1.29-.12-.27-.22-.55-.29-.83h-10.69c-.3.79-.54,1.43-.72,1.93-.18.5-.32.89-.43,1.18-.11.29-.18.5-.23.64-.04.09-.06.19-.07.29,0,.13.05.25.16.33ZM84.05,75.55l-4,10.45h8.36l-4.36-10.45Z"/>
+ <path class="logo-fill" d="M112.8,93.4c.32.19.68.29,1.2.29v.31h-9v-.31s.33-.1.65-.29c.14-.13.21-.32.35-.51v-20.46c-.14-.19-.21-.37-.35-.51-.32-.19-.65-.29-.65-.29v-.64h16.38c3.31,0,5.71.59,7.2,1.78,1.49,1.19,2.24,3.02,2.24,5.51,0,2.02-.6,3.6-1.81,4.74-1.29,1.19-2.87,2.03-4.59,2.46l5.28,7.29c.26.47.75.77,1.3.79v.43h-9v-.55c.63-.03.95-.3.95-.82-.09-.23-.2-.44-.35-.64-.23-.36-.52-.8-.87-1.32-.35-.52-.73-1.07-1.15-1.66-.42-.59-.82-1.16-1.21-1.7-.39-.55-.73-1.03-1.02-1.45-.29-.42-.48-.71-.56-.86h-4.8v7.9c-.41.19-.34.37-.2.51ZM123.63,80.25c.99-.92,1.03-2.45.09-3.42-.03-.03-.06-.06-.09-.08-.51-.5-1.45-.75-2.82-.75h-7.82v5h7.82c1.36,0,2.3-.25,2.82-.75Z"/>
+ <path class="logo-fill" d="M138.6,93.4c.14-.13.21-.32.4-.51v-20.46c-.19-.19-.26-.37-.4-.51-.32-.19-.68-.29-.6-.29v-.64h9v.64c-.57,0-.93.1-1.25.29-.14.13-.21.32.25.51v20.46c-.46.19-.39.37-.25.51.32.19.68.29,1.25.29v.31h-9v-.31c-.08,0,.28-.1.6-.29Z"/>
+ <path class="logo-fill" d="M154.51,71.93c-.32-.19-.68-.29-.51-.29v-.64h10v.53c-.36,0-.72.06-1.05.2-.15.07-.25.21-.26.37.16.29.37.55.6.79.4.47.93,1.05,1.58,1.76s1.39,1.49,2.21,2.35c.82.86,1.65,1.73,2.49,2.59.84.86,1.64,1.68,2.42,2.46.78.78,1.44,1.44,2,2v-11.73c0-.5-.33-.76-1-.79v-.53h8v.64c-.14,0-.51.1-.83.29-.14.13-.21.32-.17.51v20.46c-.04.19.04.37.17.51.32.19.68.29.83.29v.31h-8v-.53c.29.03.57-.02.83-.13.13-.07.2-.21.2-.35-.01-.12-.06-.23-.14-.33-.18-.24-.38-.47-.59-.68-.3-.32-.74-.78-1.33-1.38-.59-.6-1.36-1.4-2.32-2.42-.96-1.01-2.15-2.27-3.56-3.78-1.41-1.51-3.11-3.34-5.1-5.51v14s-.2.37-.07.51c.32.19.68.29,1.07.29v.31h-8v-.31c-.18,0,.19-.1.51-.29.14-.13.21-.32.49-.51v-20.46c-.29-.19-.36-.37-.49-.51Z"/>
+</svg>
\ No newline at end of file
diff --git a/examples/logos/text-plus-logo.svg b/examples/logos/text-plus-logo.svg
new file mode 100644
index 0000000..f750ccd
--- /dev/null
+++ b/examples/logos/text-plus-logo.svg
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg id="Ebene_2" data-name="Ebene 2" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 296 296">
+ <defs>
+ <style>
+ .cls-1 {
+ fill: #cd0022;
+ }
+ </style>
+ </defs>
+ <g id="Ebene_1-2" data-name="Ebene 1">
+ <g>
+ <path class="cls-1" d="m103.16,142.1c-2.9,0-5.12,1.08-6.68,3.23-1.09,1.5-1.78,3.48-2.11,5.91h16.82v-.97c0-2.31-.7-4.25-2.11-5.82-1.41-1.57-3.38-2.35-5.93-2.35Z"/>
+ <path class="cls-1" d="m148,0C66.26,0,0,66.26,0,148s66.26,148,148,148,148-66.26,148-148S229.74,0,148,0Zm-82.42,179.48h-15.15v-52.62h-18.49v-11.33h52.18v11.33h-18.54v52.62Zm60.07-19.55h-31.4c.25,2.93,1.06,5.28,2.43,7.03,1.68,2.14,4.12,3.21,7.31,3.21,2.31,0,4.33-.61,6.04-1.82,1.71-1.21,3.11-2.58,4.2-4.11l10.45,6.63c-1.46,3.02-4.02,5.43-7.67,7.25s-7.88,2.72-12.72,2.72c-7.61,0-13.63-2.17-18.05-6.5-4.42-4.33-6.63-10.13-6.63-17.4v-1.41c0-7.2,2.24-12.97,6.72-17.31,4.48-4.33,10.13-6.5,16.96-6.5s12.37,2.04,16.36,6.11c4,4.07,6,9.72,6,16.96v5.14Zm34.35,19.55l-9.09-14.23-9.49,14.23h-13.49l16.32-24.17-14.96-22.22h16.74l7.44,11.6,7.23-11.6h13.49l-14.02,21.66,16.53,24.73h-16.69Zm45.9-35.8h-8.17v20.86c0,2.11.39,3.55,1.16,4.33.78.78,1.79,1.16,3.05,1.16.64,0,1.28-.09,1.91-.26.63-.18,1.22-.38,1.78-.62v10.28c-.64.35-1.59.66-2.83.92-1.24.26-2.58.4-4.02.4-4.71,0-8.5-1.27-11.35-3.82s-4.28-6.56-4.28-12.04v-21.22h-6.46v-10.59h6.5v-10.76h14.54v10.76h8.17v10.59Zm40.61,12.48v15.9h-14.32v-15.9h-14.14v-12.61h14.14v-15.86h14.32v15.86h14.19v12.61h-14.19Z"/>
+ </g>
+ </g>
+</svg>
\ No newline at end of file
diff --git a/examples/minimal.Rmd b/examples/minimal.Rmd
new file mode 100644
index 0000000..ca5e595
--- /dev/null
+++ b/examples/minimal.Rmd
@@ -0,0 +1,53 @@
+---
+title: "Minimal Deck"
+subtitle: "smallest useful revealjs.ids presentation"
+author: "John Doe"
+date: "1 January 2026"
+# linked QR code in the top left of the title slide:
+website: "https://www.ids-mannheim.de/digspra/"
+# second institution logo (top right of every slide, next to the IDS logo);
+# funder logo (bottom left of the title slide). Local files are embedded:
+# partner_logo: "partner-logo.svg"
+# funder_logo: "funder-logo.svg"
+output: revealjs.ids::revealjs_presentation
+---
+
+```{r setup, include=FALSE}
+knitr::opts_chunk$set(echo = TRUE)
+```
+
+# Introduction
+
+## Markdown basics
+
+- Some **bold** text in the IDS style
+- Incremental points with `{.fragment}` or global `incremental: true`
+
+<!-- 00:30 -->
+
+::: notes
+This slide has speaker notes (press `s` for the speaker view) and a
+half-minute time budget from the timing comment above -- watch the pacing
+timer.
+:::
+
+# Results
+
+## Data frames look reasonable by default
+
+```{r}
+head(cars, 6)
+```
+
+## And plots as usual
+
+```{r pressure, fig.height=5, fig.width=10, message=FALSE}
+plot(pressure)
+```
+
+# Misc
+
+## Links
+
+A linked QR code, e.g. for supplementary material, can be put on any slide
+with inline R code: `r revealjs.ids::qrlink("https://www.ids-mannheim.de/")`
diff --git a/inst/reveal.js-5.2.1/css/theme/source/ids.scss b/inst/reveal.js-5.2.1/css/theme/source/ids.scss
index 9fd8e45..c55b95f 100644
--- a/inst/reveal.js-5.2.1/css/theme/source/ids.scss
+++ b/inst/reveal.js-5.2.1/css/theme/source/ids.scss
@@ -70,6 +70,11 @@
// doesn't exactly match the configured one.
$idsBarWidthRatio: 0.05;
$idsLogoWidthRatio: 0.25;
+// Partner (second institution) logo: shown next to the IDS logo on every
+// slide when the document sets the `partner_logo` yaml entry (the template
+// then publishes the URL as the --partner-logo custom property).
+$idsPartnerGap: 16px;
+$idsPartnerHeightRatio: 0.085;
$idsBarWidth: calc(var(--slide-height) * #{$idsBarWidthRatio});
$idsHeadingGap: 24px;
@@ -225,10 +230,16 @@
$idsLogoRight: 8px;
.reveal .slides {
- background-image: url('https://corpora.ids-mannheim.de/slides/reveal.js.ids/images/IDS-Logo-2019.svg');
+ // Second layer: the optional partner institution logo (--partner-logo is
+ // supplied by the template from the document's `partner_logo` yaml entry);
+ // paints as "none" without it, leaving a harmless empty layer.
+ background-image: url('https://corpora.ids-mannheim.de/slides/reveal.js.ids/images/IDS-Logo-2019.svg'),
+ var(--partner-logo, none);
background-repeat: no-repeat;
- background-position: top #{$idsLogoTop} right #{$idsLogoRight};
- background-size: calc(var(--slide-height) * #{$idsLogoWidthRatio}) auto;
+ background-position: top #{$idsLogoTop} right #{$idsLogoRight},
+ top #{$idsLogoTop} right calc(#{$idsLogoRight} + var(--slide-height) * #{$idsLogoWidthRatio} + #{$idsPartnerGap});
+ background-size: calc(var(--slide-height) * #{$idsLogoWidthRatio}) auto,
+ auto calc(var(--slide-height) * #{$idsPartnerHeightRatio});
}
// The orange bar is drawn as a ::before on the headings rather than on the
@@ -565,6 +576,72 @@
height: 100%;
}
+// Website QR code and funder logo on the title slide (document options
+// `website` and `funder_logo`): top-left and bottom-left corners. All
+// logos on the title slide keep the same distance to their respective
+// margins: top 6px / left 8px (QR), top 6px / right 8px (IDS [+ partner]
+// logo), bottom 10px / left 8px (funder), bottom 10px / right 8px (WGL
+// membership logo), so the four corners share one inset vocabulary. The QR
+// is sized generously (a quarter of the slide height) so it stays scannable
+// in all circumstances. Both are DOM elements injected into the title frame
+// by the pandoc template (the QR itself is generated in R by qrlink());
+// absolute positioning sticks to the slide, because reveal.js positions the
+// sections themselves.
+.reveal .slides section.title-frame .title-qr {
+ position: absolute;
+ top: #{$idsLogoTop};
+ left: #{$idsLogoRight};
+ // The generated QR matrix has no quiet zone of its own (dark modules reach
+ // the svg edges), which makes it unscannable; the white plate's padding of
+ // ~4 modules supplies it. The plate's edge is also the element's visual
+ // boundary for the uniform corner insets, hence an outer size of 28% of
+ // the slide height minus the padding on two sides (~23% of pattern).
+ box-sizing: border-box;
+ width: calc(var(--slide-height) * 0.28);
+ padding: calc(var(--slide-height) * 0.025);
+ background: #fff;
+}
+
+.reveal .slides section.title-frame .title-qr a.qrcode,
+.reveal .slides section.title-frame .title-qr svg {
+ display: block;
+ width: 100%;
+ height: auto;
+ margin: 0;
+}
+
+.reveal .slides section.title-frame img.funder-logo {
+ position: absolute;
+ left: #{$idsLogoRight};
+ bottom: 10px;
+ height: calc(var(--slide-height) * 0.07);
+ width: auto;
+}
+
+// Lift the funder logo above the print footer band, like the WGL logo above
+html.reveal-print .reveal .slides section.title-frame img.funder-logo {
+ bottom: 55px;
+}
+
+// The navigation chrome is hidden on the title slide (the template toggles
+// the ids-title-plain class when the title frame is the current slide). Use
+// opacity/visibility rather than display:none so the transition out of the
+// title slide fades the chrome in instead of popping it up, and so reveal.js'
+// own controls bookkeeping keeps working underneath.
+html.ids-title-plain .reveal .controls,
+html.ids-title-plain .reveal .slide-number,
+html.ids-title-plain .reveal .slide-menu-button {
+ opacity: 0 !important;
+ visibility: hidden !important;
+ pointer-events: none !important;
+}
+
+.reveal .controls,
+.reveal .slide-number,
+.reveal .slide-menu-button {
+ transition: opacity 0.4s;
+}
+
/*********************************************
* SLIDES MENU
diff --git a/inst/reveal.js-5.2.1/dist/theme/ids.css b/inst/reveal.js-5.2.1/dist/theme/ids.css
index 8be1e80..982e0aa 100644
--- a/inst/reveal.js-5.2.1/dist/theme/ids.css
+++ b/inst/reveal.js-5.2.1/dist/theme/ids.css
@@ -430,10 +430,12 @@
}
.reveal .slides {
- background-image: url("https://corpora.ids-mannheim.de/slides/reveal.js.ids/images/IDS-Logo-2019.svg");
+ /* Second layer: the optional partner institution logo (--partner-logo is
+ supplied by the template from the document's partner_logo yaml entry) */
+ background-image: url("https://corpora.ids-mannheim.de/slides/reveal.js.ids/images/IDS-Logo-2019.svg"), var(--partner-logo, none);
background-repeat: no-repeat;
- background-position: top 6px right 8px;
- background-size: calc(var(--slide-height) * 0.25) auto;
+ background-position: top 6px right 8px, top 6px right calc(8px + var(--slide-height) * 0.25 + 16px);
+ background-size: calc(var(--slide-height) * 0.25) auto, auto calc(var(--slide-height) * 0.085);
}
.reveal .slides section:not(.title-frame) > h1:first-child,
@@ -674,6 +676,55 @@
height: 100%;
}
+.reveal .slides section.title-frame .title-qr {
+ position: absolute;
+ top: 6px;
+ left: 8px;
+ /* The generated QR matrix has no quiet zone of its own (dark modules
+ reach the svg edges); the white plate's padding supplies it. */
+ box-sizing: border-box;
+ width: calc(var(--slide-height) * 0.28);
+ padding: calc(var(--slide-height) * 0.025);
+ background: #fff;
+}
+
+.reveal .slides section.title-frame .title-qr a.qrcode,
+.reveal .slides section.title-frame .title-qr svg {
+ display: block;
+ width: 100%;
+ height: auto;
+ margin: 0;
+}
+
+.reveal .slides section.title-frame img.funder-logo {
+ position: absolute;
+ left: 8px;
+ bottom: 10px;
+ height: calc(var(--slide-height) * 0.07);
+ width: auto;
+}
+
+/* Lift the funder logo above the print footer band, like the WGL logo */
+html.reveal-print .reveal .slides section.title-frame img.funder-logo {
+ bottom: 55px;
+}
+
+/* The navigation chrome is hidden on the title slide (the template toggles
+ the ids-title-plain class when the title frame is the current slide) */
+html.ids-title-plain .reveal .controls,
+html.ids-title-plain .reveal .slide-number,
+html.ids-title-plain .reveal .slide-menu-button {
+ opacity: 0 !important;
+ visibility: hidden !important;
+ pointer-events: none !important;
+}
+
+.reveal .controls,
+.reveal .slide-number,
+.reveal .slide-menu-button {
+ transition: opacity 0.4s;
+}
+
/*********************************************
* SLIDES MENU
*********************************************/
diff --git a/inst/rmarkdown/templates/revealjs_presentation/resources/default.html b/inst/rmarkdown/templates/revealjs_presentation/resources/default.html
index 9d49602..cf56f8e 100644
--- a/inst/rmarkdown/templates/revealjs_presentation/resources/default.html
+++ b/inst/rmarkdown/templates/revealjs_presentation/resources/default.html
@@ -130,10 +130,18 @@
</style>
$for(css)$
- <link rel="stylesheet" href="$css$"/>
-$endfor$
+ <link rel="stylesheet" href="$css$"/>
+ $endfor$
+$if(theme-ids)$
+$if(partner_logo)$
+ <!-- partner institution logo: shown next to the IDS logo on every slide.
+ Only the URL is supplied here; the geometry lives in the IDS theme
+ (added as a second background layer on .reveal .slides) -->
+ <style>:root { --partner-logo: url("$partner-logo-src$"); }</style>
+$endif$
+$endif$
$for(header-includes)$
- $header-includes$
+ $header-includes$
$endfor$
</head>
<body>
@@ -145,6 +153,16 @@
$if(theme-ids)$
$if(title)$
<section class="title-frame" id="$idprefix$title-slide">
+$if(partner_logo)$
+ <!-- partner-logo: also shown together with the IDS logo on every slide
+ (via the injected background rule in <head>) -->
+$endif$
+$if(website-qr)$
+ <div class="title-qr">$website-qr$</div>
+$endif$
+$if(funder_logo)$
+ <img class="funder-logo" src="$funder-logo-src$" alt="" />
+$endif$
<div class="author">
$for(author)$
$if(author.name)$
@@ -765,6 +783,23 @@
$if(theme-ids)$
<script>
+ // IDS theme: the navigation chrome (arrows, slide number, menu button) is
+ // hidden on the title slide -- it already carries title, authors, logos and
+ // QR code, and the bare first impression follows the IDS reference decks.
+ (function () {
+ function toggle() {
+ var slide = Reveal.getCurrentSlide();
+ document.documentElement.classList.toggle('ids-title-plain',
+ !!(slide && slide.classList.contains('title-frame')));
+ }
+ Reveal.on('ready', toggle);
+ Reveal.on('slidechanged', toggle);
+ })();
+</script>
+$endif$
+
+$if(theme-ids)$
+<script>
// IDS theme: long slide headings should shrink onto one line instead of
// wrapping (and pushing the orange bar taller or overflowing the slide).
// Slide content is laid out in fixed slide coordinates and only ever scaled
diff --git a/man/qrlink.Rd b/man/qrlink.Rd
new file mode 100644
index 0000000..26b5bd7
--- /dev/null
+++ b/man/qrlink.Rd
@@ -0,0 +1,31 @@
+% Generated by roxygen2: do not edit by hand
+% Please edit documentation in R/utils.R
+\name{qrlink}
+\alias{qrlink}
+\title{HTML code for a linked QR code}
+\usage{
+qrlink(url, logo = NULL, text = NULL, caption = text)
+}
+\arguments{
+\item{url}{Target URL for the QR code}
+
+\item{logo}{Optional path to a logo image to place in the center of the QR
+code}
+
+\item{text}{Optional caption displayed below the QR code (alias:
+\code{caption})}
+
+\item{caption}{Alias for \code{text}}
+}
+\value{
+Character string with HTML code for a linked QR code
+}
+\description{
+Generates a QR code (inline SVG) linking to the given URL and returns the
+corresponding HTML code, so a QR code can be placed anywhere in a
+presentation with inline R code.
+}
+\details{
+The \code{website} YAML entry of \code{\link[=revealjs_presentation]{revealjs_presentation()}} uses this to
+place a linked QR code in the top left of the title slide.
+}
diff --git a/man/revealjs_presentation.Rd b/man/revealjs_presentation.Rd
index 7f48a89..9735960 100644
--- a/man/revealjs_presentation.Rd
+++ b/man/revealjs_presentation.Rd
@@ -313,6 +313,30 @@
}\if{html}{\out{</div>}}
}
+\subsection{Title slide: QR code and additional logos}{
+
+Three optional yaml entries personalise the title slide for external
+audiences (ported from \pkg{posterdown.ids}):
+\itemize{
+\item \verb{website: <url>} -- a linked QR code pointing to the URL is placed in
+the top left of the title slide. Uses the \pkg{qrcode} package.
+\item \verb{partner_logo: <path-or-url>} -- when a presentation is given by
+authors from two institutions, the partner institution's logo is shown
+next to the IDS logo, in the top right of every slide.
+\item \verb{funder_logo: <path-or-url>} -- a funder logo (e.g. "Text+") is placed
+in the bottom left of the title slide, opposite the Leibniz
+Gemeinschaft membership logo. It is lifted above the footer band on
+print (pdf) pages.
+}
+
+Local logo files (svg, png, jpg, gif, webp) are embedded in the html as
+data URIs, so they do not need to be shipped with the presentation; http
+URLs are used as-is. A QR code anywhere else on a slide can be produced
+with \code{\link[=qrlink]{qrlink()}} from inline R code. The navigation chrome (arrows, slide
+number, menu button) is hidden on the title slide for a bare first
+impression.
+}
+
\subsection{Bibliography and citations}{
Documents with a \verb{bibliography:} in their YAML header are formatted with
diff --git a/tests/testthat/test-branding.R b/tests/testthat/test-branding.R
new file mode 100644
index 0000000..df699b0
--- /dev/null
+++ b/tests/testthat/test-branding.R
@@ -0,0 +1,43 @@
+test_that("title slide branding works (website QR, partner_logo, funder_logo)", {
+ skip_on_cran()
+ skip_if_not_installed("qrcode")
+ skip_if_not_installed("base64enc")
+
+ logo_svg <- tempfile(fileext = ".svg")
+ writeLines(
+ '<svg xmlns="http://www.w3.org/2000/svg" width="10" height="10"><rect width="10" height="10"/></svg>',
+ logo_svg
+ )
+
+ doc <- tempfile(fileext = ".Rmd")
+ writeLines(
+ sprintf(
+ '---
+title: "Test"
+output: revealjs.ids::revealjs_presentation
+website: "https://www.ids-mannheim.de/"
+partner_logo: "%s"
+funder_logo: "%s"
+---
+
+## A slide
+',
+ logo_svg, logo_svg
+ ),
+ doc
+ )
+
+ out <- rmarkdown::render(doc, quiet = TRUE)
+ html <- paste(readLines(out, warn = FALSE), collapse = "\n")
+
+ expect_match(html, '<div class="title-qr">', fixed = TRUE)
+ expect_match(html, '<a class="qrcode" href="https://www.ids-mannheim.de/">',
+ fixed = TRUE
+ )
+ expect_match(html, "partner-logo: url(\"data:image/svg+xml;base64,",
+ fixed = TRUE
+ )
+ expect_match(html, '<img class="funder-logo" src="data:image/svg+xml;base64,',
+ fixed = TRUE
+ )
+})