1#' Convert to an HTML document 2#' 3#' Format for converting from R Markdown to an HTML document. 4#' 5#' See the \href{https://bookdown.org/yihui/rmarkdown/html-document.html}{online 6#' documentation} for additional details on using the \code{html_document} 7#' format. 8#' 9#' R Markdown documents can have optional metadata that is used to generate a 10#' document header that includes the title, author, and date. For more details 11#' see the documentation on R Markdown \link[=rmd_metadata]{metadata}. 12#' 13#' R Markdown documents also support citations. You can find more information on 14#' the markdown syntax for citations in the 15#' \href{https://pandoc.org/MANUAL.html#citations}{Bibliographies 16#' and Citations} article in the online documentation. 17#' 18#'@inheritParams output_format 19#'@param toc \code{TRUE} to include a table of contents in the output 20#'@param toc_depth Depth of headers to include in table of contents 21#'@param toc_float \code{TRUE} to float the table of contents to the left of the 22#' main document content. Rather than \code{TRUE} you may also pass a list of 23#' options that control the behavior of the floating table of contents. See the 24#' \emph{Floating Table of Contents} section below for details. 25#'@param number_sections \code{TRUE} to number section headings 26#'@param anchor_sections \code{TRUE} to show section anchors when mouse hovers. 27#' See \link[rmarkdown:html_document]{Anchor Sections Customization section}. 28#'@param fig_width Default width (in inches) for figures 29#'@param fig_height Default height (in inches) for figures 30#'@param fig_retina Scaling to perform for retina displays (defaults to 2, which 31#' currently works for all widely used retina displays). Set to \code{NULL} to 32#' prevent retina scaling. Note that this will always be \code{NULL} when 33#' \code{keep_md} is specified (this is because \code{fig_retina} relies on 34#' outputting HTML directly into the markdown document). 35#'@param fig_caption \code{TRUE} to render figures with captions 36#'@param dev Graphics device to use for figure output (defaults to png) 37#'@param code_folding Enable document readers to toggle the display of R code 38#' chunks. Specify \code{"none"} to display all code chunks (assuming 39#' they were knit with \code{echo = TRUE}). Specify \code{"hide"} to hide all R 40#' code chunks by default (users can show hidden code chunks either 41#' individually or document-wide). Specify \code{"show"} to show all R code 42#' chunks by default. 43#'@param code_download Embed the Rmd source code within the document and provide 44#' a link that can be used by readers to download the code. 45#'@param self_contained Produce a standalone HTML file with no external 46#' dependencies, using data: URIs to incorporate the contents of linked 47#' scripts, stylesheets, images, and videos. Note that even for self contained 48#' documents MathJax is still loaded externally (this is necessary because of 49#' its size). 50#'@param theme One of the following: 51#' * A [bslib::bs_theme()] object (or a list of [bslib::bs_theme()] argument values) 52#' * Use this option for custom themes using Bootstrap 4 or 3. 53#' * In this case, any `.scss`/`.sass` files provided to the `css` 54#' parameter may utilize the `theme`'s underlying Sass utilities 55#' (e.g., variables, mixins, etc). 56#' * `NULL` for no theme (i.e., no [html_dependency_bootstrap()]). 57#' * A character string specifying a [Bootswatch 3](https://bootswatch.com/3/) 58#' theme name (for backwards-compatibility). 59#'@param highlight Syntax highlighting style. Supported styles include 60#' "default", "tango", "pygments", "kate", "monochrome", "espresso", "zenburn", 61#' "haddock", and "textmate". Pass \code{NULL} to prevent syntax highlighting. 62#'@param mathjax Include mathjax. The "default" option uses an https URL from a 63#' MathJax CDN. The "local" option uses a local version of MathJax (which is 64#' copied into the output directory). You can pass an alternate URL or pass 65#' \code{NULL} to exclude MathJax entirely. 66#'@param section_divs Wrap sections in \code{<div>} tags, and attach identifiers to the 67#' enclosing \code{<div>} rather than the header itself. 68#'@param template Pandoc template to use for rendering. Pass "default" to use 69#' the rmarkdown package default template; pass \code{NULL} to use pandoc's 70#' built-in template; pass a path to use a custom template that you've created. 71#' Note that if you don't use the "default" template then some features of 72#' \code{html_document} won't be available (see the Templates section below for 73#' more details). 74#'@param css CSS and/or Sass files to include. Files with an extension of .sass 75#' or .scss are compiled to CSS via `sass::sass()`. Also, if `theme` is a 76#' [bslib::bs_theme()] object, Sass code may reference the relevant Bootstrap 77#' Sass variables, functions, mixins, etc. 78#'@param includes Named list of additional content to include within the 79#' document (typically created using the \code{\link{includes}} function). 80#'@param keep_md Keep the markdown file generated by knitting. 81#'@param lib_dir Directory to copy dependent HTML libraries (e.g. jquery, 82#' bootstrap, etc.) into. By default this will be the name of the document with 83#' \code{_files} appended to it. 84#'@param md_extensions Markdown extensions to be added or removed from the 85#' default definition or R Markdown. See the \code{\link{rmarkdown_format}} for 86#' additional details. 87#'@param pandoc_args Additional command line options to pass to pandoc 88#'@param extra_dependencies,... Additional function arguments to pass to the 89#' base R Markdown HTML output formatter \code{\link{html_document_base}} 90#'@return R Markdown output format to pass to \code{\link{render}} 91#' 92#'@section Anchor Sections Customization: 93#' By default, a \samp{#} is used as a minimalist choice, referring to the id selector 94#' in HTML and CSS. You can easily change that using a css rule in your 95#' document. For example, to add a \href{https://codepoints.net/U+1F517}{link 96#' symbol} \if{html}{\out{(🔗︎)}} instead: 97#' \preformatted{ 98#' a.anchor-section::before { 99#' content: '\\01F517\\00FE0E'; 100#' }} 101#' You can remove \samp{\\00FE0E} to get a more complex link pictogram 102#' \if{html}{\out{(🔗)}}. 103#' 104#' If you prefer an svg icon, you can also use one using for example a direct link or downloading it from 105#' \url{https://fonts.google.com/icons}. 106#' \preformatted{ 107#' /* From https://fonts.google.com/icons 108#' Licence: https://www.apache.org/licenses/LICENSE-2.0.html */ 109#' a.anchor-section::before { 110#' content: url(https://fonts.gstatic.com/s/i/materialicons/link/v7/24px.svg); 111#' }} 112#' 113#' About how to apply custom CSS, see 114#' \url{https://bookdown.org/yihui/rmarkdown-cookbook/html-css.html} 115#' 116#'@section Navigation Bars: 117#' 118#' If you have a set of html documents which you'd like to provide a common 119#' global navigation bar for, you can include a "_navbar.yml" or "_navbar.html" 120#' file within the same directory as your html document and it will automatically 121#' be included at the top of the document. 122#' 123#' The "_navbar.yml" file includes \code{title}, \code{type}, \code{left}, and 124#' \code{right} fields (to define menu items for the left and right of the navbar 125#' respectively). Menu items include \code{title} and \code{href} fields. For example: 126#' 127#' \preformatted{title: "My Website" 128#' type: default 129#' left: 130#' - text: "Home" 131#' href: index.html 132#' - text: "Other" 133#' href: other.html 134#' right: 135#' - text: GitHub 136#' href: https://github.com} 137#' The \code{type} field is optional and can take the value "default" or "inverse" (which 138#' provides a different color scheme for the navigation bar). 139#' 140#' Alternatively, you can include a "_navbar.html" file which is a full HTML definition 141#' of a bootstrap navigation bar. For a simple example of including a navigation bar see 142#' \url{https://github.com/rstudio/rmarkdown-website/blob/master/_navbar.html}. 143#' For additional documentation on creating Bootstrap navigation bars see 144#' \url{https://getbootstrap.com/docs/4.5/components/navbar/}. 145#' 146#' 147#'@section Floating Table of Contents: 148#' 149#' You may specify a list of options for the \code{toc_float} parameter which 150#' control the behavior of the floating table of contents. Options include: 151#' 152#' \itemize{ \item{\code{collapsed} (defaults to \code{TRUE}) controls whether 153#' the table of contents appears with only the top-level (H2) headers. When 154#' collapsed the table of contents is automatically expanded inline when 155#' necessary.} \item{\code{smooth_scroll} (defaults to \code{TRUE}) controls 156#' whether page scrolls are animated when table of contents items are navigated 157#' to via mouse clicks.} \item{\code{print} (defaults to \code{TRUE}) controls 158#' whether the table of contents appears when user prints out the HTML page.}} 159#' 160#'@section Tabbed Sections: 161#' 162#' You can organize content using tabs by applying the \code{.tabset} class 163#' attribute to headers within a document. This will cause all sub-headers of 164#' the header with the \code{.tabset} attribute to appear within tabs rather 165#' than as standalone sections. For example: 166#' 167#' \preformatted{## Quarterly Results {.tabset} 168#' 169#' ### By Product 170#' 171#' ### By Region } 172#' 173#' You can also specify two additional attributes to control the appearance and 174#' behavior of the tabs. The \code{.tabset-fade} attributes causes the tabs to 175#' fade in and out when switching. The \code{.tabset-pills} attribute causes 176#' the visual appearance of the tabs to be "pill" rather than traditional tabs. 177#' For example: 178#' 179#' \preformatted{## Quarterly Results {.tabset .tabset-fade .tabset-pills}} 180#' 181#'@section Templates: 182#' 183#' You can provide a custom HTML template to be used for rendering. The syntax 184#' for templates is described in the 185#' \href{https://pandoc.org/MANUAL.html}{pandoc documentation}. You can also use 186#' the basic pandoc template by passing \code{template = NULL}. 187#' 188#' Note however that if you choose not to use the "default" HTML template then 189#' several aspects of HTML document rendering will behave differently: 190#' 191#' \itemize{ \item{The \code{theme} parameter does not work (you can still 192#' provide styles using the \code{css} parameter). } \item{For the 193#' \code{highlight} parameter, the default highlighting style will resolve to 194#' "pygments" and the "textmate" highlighting style is not available } 195#' \item{The \code{toc_float} parameter will not work. } \item{The 196#' \code{code_folding} parameter will not work. } \item{Tabbed sections (as 197#' described above) will not work.} \item{Navigation bars (as described above) 198#' will not work. }\item{MathJax will not work if \code{self_contained} is 199#' \code{TRUE} (these two options can't be used together in normal pandoc 200#' templates). } } 201#' 202#' Due to the above restrictions, you might consider using the \code{includes} 203#' parameter as an alternative to providing a fully custom template. 204#' 205#' @examples 206#' \dontrun{ 207#' library(rmarkdown) 208#' 209#' render("input.Rmd", html_document()) 210#' 211#' render("input.Rmd", html_document(toc = TRUE)) 212#' } 213#' @md 214#' @export 215html_document <- function(toc = FALSE, 216 toc_depth = 3, 217 toc_float = FALSE, 218 number_sections = FALSE, 219 anchor_sections = FALSE, 220 section_divs = TRUE, 221 fig_width = 7, 222 fig_height = 5, 223 fig_retina = 2, 224 fig_caption = TRUE, 225 dev = 'png', 226 df_print = "default", 227 code_folding = c("none", "show", "hide"), 228 code_download = FALSE, 229 self_contained = TRUE, 230 theme = "default", 231 highlight = "default", 232 mathjax = "default", 233 template = "default", 234 extra_dependencies = NULL, 235 css = NULL, 236 includes = NULL, 237 keep_md = FALSE, 238 lib_dir = NULL, 239 md_extensions = NULL, 240 pandoc_args = NULL, 241 ...) { 242 243 # build pandoc args 244 args <- c("--standalone") 245 246 # use section divs 247 if (section_divs) 248 args <- c(args, "--section-divs") 249 250 # table of contents 251 args <- c(args, pandoc_toc_args(toc, toc_depth)) 252 253 # makes downstream logic easier to reason about 254 theme <- resolve_theme(theme) 255 256 # toc_float 257 if (toc && !identical(toc_float, FALSE)) { 258 259 # must have a theme 260 if (is.null(theme)) 261 stop("You must use a theme when specifying the 'toc_float' option") 262 263 # resolve options 264 toc_float_options <- list(collapsed = TRUE, 265 smooth_scroll = TRUE, 266 print = TRUE) 267 if (is.list(toc_float)) { 268 toc_float_options <- merge_lists(toc_float_options, toc_float) 269 toc_float <- TRUE 270 } else if (!isTRUE(toc_float)) { 271 stop("toc_float must be a logical or a list with options") 272 } 273 274 # dependencies 275 extra_dependencies <- append(extra_dependencies, 276 list(html_dependency_jquery(), 277 html_dependency_jqueryui(), 278 html_dependency_tocify())) 279 280 # flag for template 281 args <- c(args, pandoc_variable_arg("toc_float", "1")) 282 283 # selectors 284 selectors <- paste0("h", seq(1, toc_depth), collapse = ",") 285 args <- c(args, pandoc_variable_arg("toc_selectors", selectors)) 286 287 # options 288 if (toc_float_options$collapsed) 289 args <- c(args, pandoc_variable_arg("toc_collapsed", "1")) 290 if (toc_float_options$smooth_scroll) 291 args <- c(args, pandoc_variable_arg("toc_smooth_scroll", "1")) 292 if (toc_float_options$print) 293 args <- c(args, pandoc_variable_arg("toc_print", "1")) 294 } 295 296 # template path and assets 297 template_file <- if (identical(template, "default")) { 298 pkg_file("rmd/h/default.html") 299 } else template 300 if (!is.null(template_file)) 301 args <- c(args, "--template", pandoc_path_arg(template_file)) 302 303 # validate code_folding 304 code_folding <- match.arg(code_folding) 305 306 # navigation dependencies 307 if (!is.null(theme)) { 308 code_menu <- !identical(code_folding, "none") || code_download 309 source_embed <- code_download 310 extra_dependencies <- append(extra_dependencies, 311 list( 312 html_dependency_jquery(), 313 html_dependency_navigation(code_menu = code_menu, 314 source_embed = source_embed) 315 ) 316 ) 317 } 318 319 # highlight 320 args <- c(args, pandoc_html_highlight_args(template, highlight)) 321 322 # add highlight.js html_dependency if required 323 extra_dependencies <- append( 324 extra_dependencies, 325 if (identical(template, "default") && is_highlightjs(highlight)) { 326 list(html_dependency_highlightjs(highlight)) 327 } else if (!is.null(highlight)) { 328 # for screen-reader accessibility improvement 329 list(html_dependency_accessible_code_block()) 330 } 331 ) 332 333 # numbered sections 334 if (number_sections) 335 args <- c(args, "--number-sections") 336 337 338 # manage list of exit_actions (backing out changes to knitr options) 339 exit_actions <- list() 340 on_exit <- function() { 341 for (action in exit_actions) 342 try(action()) 343 } 344 345 # capture the source code if requested 346 source_code <- NULL 347 source_file <- NULL 348 pre_knit <- function(input, ...) { 349 if (code_download) { 350 source_file <<- basename(input) 351 source_code <<- paste0( 352 '<div id="rmd-source-code">', 353 xfun::base64_encode(input), 354 '</div>') 355 } 356 } 357 358 # pagedtable 359 if (identical(df_print, "paged")) { 360 extra_dependencies <- append(extra_dependencies, 361 list(html_dependency_pagedtable())) 362 } 363 364 # anchor-sections 365 if (anchor_sections) { 366 extra_dependencies <- append(extra_dependencies, 367 list(html_dependency_anchor_sections())) 368 } 369 370 # pre-processor for arguments that may depend on the name of the 371 # the input file AND which need to inject html dependencies 372 # (otherwise we could just call the pre_processor) 373 post_knit <- function(metadata, input_file, runtime, ...) { 374 375 # extra args 376 args <- c() 377 378 # navbar (requires theme) 379 if (!is.null(theme)) { 380 381 # add navbar to includes if necessary 382 navbar <- file.path(normalize_path(dirname(input_file)), "_navbar.html") 383 384 # if there is no _navbar.html look for a _navbar.yml 385 if (!file.exists(navbar)) { 386 navbar_yaml <- file.path(dirname(navbar), "_navbar.yml") 387 if (file.exists(navbar_yaml)) 388 navbar <- navbar_html_from_yaml(navbar_yaml) 389 # if there is no _navbar.yml then look in site config (if we have it) 390 config <- site_config(input_file) 391 if (!is.null(config) && !is.null(config$navbar)) 392 navbar <- navbar_html(config$navbar) 393 } 394 395 if (file.exists(navbar)) { 396 397 # include the navbar html 398 includes <- list(before_body = navbar) 399 args <- c(args, includes_to_pandoc_args(includes, 400 filter = if (is_shiny_classic(runtime)) 401 function(x) normalize_path(x, mustWork = FALSE) 402 else 403 identity)) 404 405 # flag indicating we need extra navbar css and js 406 args <- c(args, pandoc_variable_arg("navbar", "1")) 407 408 # navbar icon dependencies 409 iconDeps <- navbar_icon_dependencies(navbar) 410 if (length(iconDeps) > 0) 411 knitr::knit_meta_add(list(iconDeps)) 412 } 413 } 414 415 args 416 } 417 418 # pre-processor for arguments that may depend on the name of the 419 # the input file (e.g. ones that need to copy supporting files) 420 pre_processor <- function(metadata, input_file, runtime, knit_meta, files_dir, 421 output_dir) { 422 423 # use files_dir as lib_dir if not explicitly specified 424 if (is.null(lib_dir)) 425 lib_dir <- files_dir 426 427 # extra args 428 args <- c() 429 430 # track whether we have a code menu 431 code_menu <- FALSE 432 433 # code_folding 434 if (code_folding %in% c("show", "hide")) { 435 # must have a theme 436 if (is.null(theme)) 437 stop("You must use a theme when specifying the 'code_folding' option") 438 args <- c(args, pandoc_variable_arg("code_folding", code_folding)) 439 code_menu <- TRUE 440 } 441 442 # source_embed 443 if (code_download) { 444 if (is.null(theme)) 445 stop("You must use a theme when specifying the 'code_download' option") 446 args <- c(args, pandoc_variable_arg("source_embed", source_file)) 447 sourceCodeFile <- tempfile(fileext = ".html") 448 write_utf8(source_code, sourceCodeFile) 449 args <- c(args, pandoc_include_args(after_body = sourceCodeFile)) 450 code_menu <- TRUE 451 } 452 453 # code menu 454 if (code_menu) 455 args <- c(args, pandoc_variable_arg("code_menu", "1")) 456 457 # content includes (we do this here so that user include-in-header content 458 # goes after dependency generated content). make the paths absolute if 459 # making a Shiny document so we can resolve them even if rendering 460 # elsewhere. 461 args <- c(args, includes_to_pandoc_args(includes, 462 filter = if (is_shiny_classic(runtime)) 463 function(x) normalize_path(x, mustWork = FALSE) 464 else 465 identity)) 466 467 # return additional args 468 args 469 } 470 471 # return format 472 output_format( 473 knitr = knitr_options_html(fig_width, fig_height, fig_retina, keep_md, dev), 474 pandoc = pandoc_options(to = "html", 475 from = from_rmarkdown(fig_caption, md_extensions), 476 args = args), 477 keep_md = keep_md, 478 clean_supporting = self_contained, 479 df_print = df_print, 480 pre_knit = pre_knit, 481 post_knit = post_knit, 482 pre_processor = pre_processor, 483 on_exit = on_exit, 484 base_format = html_document_base(theme = theme, 485 self_contained = self_contained, 486 lib_dir = lib_dir, mathjax = mathjax, 487 template = template, 488 pandoc_args = pandoc_args, 489 extra_dependencies = extra_dependencies, 490 css = css, 491 ...) 492 ) 493} 494 495 496#' Knitr options for an HTML output format 497#' 498#' Define knitr options for an R Markdown output format that creates 499#' HTML output. 500#' 501#' @inheritParams html_document 502#' @return An list that can be passed as the \code{knitr} argument of the 503#' \code{\link{output_format}} function. 504#' @seealso \link{knitr_options}, \link{output_format} 505#' @export 506knitr_options_html <- function(fig_width, 507 fig_height, 508 fig_retina, 509 keep_md, 510 dev = 'png') { 511 512 opts_chunk <- list(dev = dev, 513 dpi = 96, 514 fig.width = fig_width, 515 fig.height = fig_height, 516 fig.retina = fig_retina) 517 518 if (keep_md) 519 opts_chunk$fig.retina <- NULL 520 521 knitr_options(opts_chunk = opts_chunk) 522} 523 524# CSS files in inst/rmd/h/bootstrap/css 525themes <- function() { 526 c("default", # keep for backward compatibility reason, changed to 'bootstrap' internally 527 "bootstrap", 528 "cerulean", 529 "cosmo", 530 "darkly", 531 "flatly", 532 "journal", 533 "lumen", 534 "paper", 535 "readable", 536 "sandstone", 537 "simplex", 538 "spacelab", 539 "united", 540 "yeti") 541} 542 543html_highlighters <- function() { 544 c(highlighters(), "textmate") 545} 546 547default_mathjax <- function() { 548 paste0("https://mathjax.rstudio.com/latest/", mathjax_config()) 549} 550 551mathjax_config <- function() { 552 "MathJax.js?config=TeX-AMS-MML_HTMLorMML" 553} 554 555 556navbar_html_from_yaml <- function(navbar_yaml) { 557 558 # parse the yaml 559 navbar <- yaml_load_file(navbar_yaml) 560 561 # generate the html 562 navbar_html(navbar) 563} 564 565 566#' Create a navbar HTML file from a navbar definition 567#' 568#' @param navbar Navbar definition 569#' @param links List of navbar links 570#' @return Path to temporary file with navbar definition 571#' @keywords internal 572#' @export 573navbar_html <- function(navbar) { 574 575 # title and type 576 if (is.null(navbar$title)) navbar$title <- "" 577 if (is.null(navbar$type)) navbar$type <- "default" 578 579 # menu entries 580 left <- navbar_links_html(navbar$left) 581 right <- navbar_links_html(navbar$right) 582 583 # build the navigation bar and return it as a temp file 584 template <- file_string(pkg_file("rmd/h/_navbar.html")) 585 navbar_html <- sprintf(template, navbar$type, navbar$title, left, right) 586 as_tmpfile(navbar_html) 587} 588 589#' @keywords internal 590#' @name navbar_html 591#' @export 592navbar_links_html <- function(links) { 593 as.character(navbar_links_tags(links)) 594} 595 596navbar_links_tags <- function(links, depth = 0L) { 597 598 if (!is.null(links)) { 599 600 tags <- lapply(links, function(x) { 601 602 if (!is.null(x$menu)) { 603 604 # sub-menu 605 is_submenu <- depth > 0L 606 607 if (is_submenu) { 608 menu_class <- "dropdown-submenu" 609 link_text <- navbar_link_text(x) 610 } else { 611 menu_class <- "dropdown" 612 link_text <- navbar_link_text(x, " ", tags$span(class = "caret")) 613 } 614 615 submenuLinks <- navbar_links_tags(x$menu, depth = depth + 1L) 616 617 tags$li(class = menu_class, 618 tags$a( 619 href = "#", class = "dropdown-toggle", 620 `data-toggle` = "dropdown", role = "button", 621 `aria-expanded` = "false", link_text), 622 tags$ul(class = "dropdown-menu", role = "menu", submenuLinks) 623 ) 624 625 } else if (!is.null(x$text) && grepl("^\\s*-{3,}\\s*$", x$text)) { 626 627 # divider 628 tags$li(class = "divider") 629 630 } else if (!is.null(x$text) && is.null(x$href)) { 631 632 # header 633 tags$li(class = "dropdown-header", x$text) 634 635 } else { 636 637 # standard menu item 638 textTags <- navbar_link_text(x) 639 tags$li(tags$a(href = x$href, textTags)) 640 } 641 }) 642 tagList(tags) 643 } else { 644 tagList() 645 } 646} 647 648navbar_link_text <- function(x, ...) { 649 650 if (!is.null(x$icon)) { 651 # find the iconset 652 split <- strsplit(x$icon, "-") 653 if (length(split[[1]]) > 1) 654 iconset <- split[[1]][[1]] 655 else 656 iconset <- "" 657 # check if a full class is passed for fontawesome = V5 658 # Add fa deprecated fa prefix otherwise = V4 compatibility 659 # https://github.com/rstudio/rmarkdown/issues/1554 660 class = if (grepl("^fa\\w? fa", iconset)) { 661 # Fontawesome 5 - full new prefix + name must be passed 662 # if old fa prefix is passed - keep it for compatibility 663 x$icon 664 } else if (iconset == "fa") { 665 # Fontawesome 4 compatibility - Add deprecated fa prefix 666 paste("fa", x$icon) 667 } else { 668 # Other Icon sets 669 paste(iconset, x$icon) 670 } 671 tagList(tags$span(class = class), " ", x$text, ...) 672 } 673 else 674 tagList(x$text, ...) 675} 676 677