slide timing: per-speaker timing comments activate speaker view pacing

Port the Google-Docs slide workflow convention: timing comments like
<!-- JD 00:30 --> (speaker code plus MM:SS/HH:MM:SS/seconds duration, or
just <!-- 00:30 --> for single-speaker talks) are converted into
data-timing attributes on the rendered slides' <section> elements, and
the calculated grand total is passed to reveal.js as the totalTime
config value, activating the pacing timer in the speaker view. A
document-supplied totalTime or defaultTiming takes precedence.

A per-speaker time summary is printed during rendering; the new
slide_timing() function computes slide, speaker, and grand totals
directly from an .Rmd source file without rendering it.

Change-Id: I89d37baab06c6ee8be7ab777a70b9b21295c8780
diff --git a/R/revealjs_presentation.R b/R/revealjs_presentation.R
index 21212a7..c35cdeb 100644
--- a/R/revealjs_presentation.R
+++ b/R/revealjs_presentation.R
@@ -33,6 +33,47 @@
 #' ```
 #' to create notes only viewable in presentation mode.
 #' 
+#' ## Slide timing
+#' 
+#' Speaker time can be planned per slide and per speaker by adding a comment
+#' with a speaker code and the expected duration (in `MM:SS`, `HH:MM:SS`, or
+#' plain seconds format) anywhere inside a slide:
+#' 
+#' ```markdown
+#' ## My slide title
+#' 
+#' Some content
+#' 
+#' <!-- MK 00:30 -->
+#' ```
+#' 
+#' This means Marc Kupietz will need 30 seconds for that slide. For talks
+#' presented by a single speaker, the speaker code can be omitted:
+#' `<!-- 00:30 -->`.
+#' 
+#' Typically you start from a known total time budget and adjust the
+#' individual slides to it. You can set this budget yourself with the
+#' reveal.js `totalTime` option (in seconds):
+#' 
+#' ```yaml
+#' output:
+#'   revealjs.ids::revealjs_presentation:
+#'     reveal_options:
+#'       totalTime: 3600 # one hour
+#' ```
+#' 
+#' During rendering, comments are converted into `data-timing` attributes on
+#' the slides' `<section>` elements (several speakers per slide are summed
+#' up), and the calculated grand total is passed to reveal.js as the
+#' `totalTime` config value. This activates the pacing timer in the
+#' [speaker view](https://revealjs.com/speaker-view/), which shows how you
+#' are doing relative to your plan. A summary of the total time per speaker
+#' is printed during rendering. If the document sets `totalTime` or
+#' `defaultTiming` itself (via `reveal_options`), those take precedence.
+#'
+#' The function [slide_timing()] computes these totals directly from an
+#' `.Rmd` source file without rendering it.
+#' 
 #' ### Search
 #' 
 #' When opt-in, it is possible to show a search box when pressing `CTRL + SHIFT +
@@ -343,6 +384,21 @@
     args
   }
 
+  # post-processor converting <!-- MK 00:30 --> speaker timing comments into
+  # data-timing attributes on the enclosing sections (activating the pacing
+  # timer of the notes plugin) and reporting per-speaker totals
+  post_processor <- function(metadata, input_file, output_file, clean, verbose) {
+    lines <- readLines(output_file, warn = FALSE, encoding = "UTF-8")
+    result <- apply_timing_attributes(lines)
+    comments <- attr(result, "comments")
+    if (nrow(comments) > 0) {
+      result <- inject_total_time(result, sum(comments$seconds))
+      writeLines(enc2utf8(result), output_file, useBytes = TRUE)
+      message(timing_report_message(comments))
+    }
+    output_file
+  }
+
   # return format
   output_format(
     knitr = knitr_options_html(fig_width, fig_height, fig_retina, keep_md),
@@ -354,6 +410,7 @@
     keep_md = keep_md,
     clean_supporting = self_contained,
     pre_processor = pre_processor,
+    post_processor = post_processor,
     base_format = html_document_base(
       lib_dir = lib_dir,
       self_contained = self_contained,