@@ -26,10 +26,11 @@ registry$serializers <- list()
2626# '
2727# ' @export
2828# '
29- register_serializer <- function (name , fun , mime_type ) {
29+ register_serializer <- function (name , fun , mime_type , graphic = FALSE ) {
3030 check_function(fun )
3131 check_string(mime_type )
3232 check_string(name )
33+ check_bool(graphic )
3334 if (grepl(" /" , name , fixed = TRUE )) {
3435 cli :: cli_abort(
3536 " {.arg name} must not contain the forward slash character ({.field /})"
@@ -40,7 +41,11 @@ register_serializer <- function(name, fun, mime_type) {
4041 " {.arg name} must not be {.val {c('...', 'none')}}"
4142 )
4243 }
43- registry $ serializers [[name ]] <- list (fun = fun , type = mime_type )
44+ registry $ serializers [[name ]] <- list (
45+ fun = fun ,
46+ type = mime_type ,
47+ graphic = graphic
48+ )
4449 invisible (NULL )
4550}
4651
@@ -51,13 +56,18 @@ register_serializer <- function(name, fun, mime_type) {
5156# ' * Any unnamed elements containing a character vector will be considered as
5257# ' names of registered serializers constructed with default values. The
5358# ' special value `"..."` will fetch all the serializers that are otherwise not
54- # ' specified in the call
59+ # ' specified in the call.
5560# ' * Any element containing a function are considered as a provided serializer
5661# ' and the element must be named by the mime type the serializer understands
5762# ' * Any remaining named elements will be considered names of registered
5863# ' serializers that should be constructed with the arguments given in the
5964# ' element
6065# '
66+ # ' @note Using the `...` will provide remaining graphics serializers if a
67+ # ' graphics serializer is explicitely requested elsewhere. Otherwise it will
68+ # ' provide the remaining non-graphics serializers. A warning is thrown if a mix
69+ # ' of graphics and non-graphics serializers are requested.
70+ # '
6171# ' @export
6272get_serializers <- function (serializers = NULL ) {
6373 if (is.null(serializers )) {
@@ -97,13 +107,11 @@ get_serializers <- function(serializers = NULL) {
97107 return (list2(!! elem_names [i ] : = serializers [[i ]]))
98108 }
99109 if (elem_names [i ] == " " && is_character(serializers [[i ]])) {
100- if (any(grepl(" /" , serializers [[i ]], fixed = TRUE ))) {
101- cli :: cli_abort(" mime types must be provided with a function" )
102- }
103110 return (get_serializers_internal(
104111 serializers [[i ]],
105112 env = env ,
106- dots_serializers = dots_serializers
113+ dots_serializers = dots_serializers ,
114+ prune_dots = FALSE
107115 ))
108116 }
109117 if (elem_names [i ] != " " ) {
@@ -119,13 +127,29 @@ get_serializers <- function(serializers = NULL) {
119127 }
120128 cli :: cli_abort(" Don't know how to parse element {i} in {.arg serializers}" )
121129 })
122- unlist(serializers , recursive = FALSE )
130+ from_dots <- unlist(lapply(
131+ serializers ,
132+ function (x ) attr(x , " from_dots" ) %|| % rep_along(x , FALSE )
133+ ))
134+ serializers <- unlist(serializers , recursive = FALSE )
135+ is_graphics <- vapply(serializers , is_device_formatter , logical (1 ))
136+ use_graphics <- if (all(from_dots )) FALSE else is_graphics [! from_dots ][1 ]
137+ keep <- ! from_dots | is_graphics == use_graphics
138+ serializers <- serializers [keep ]
139+ is_graphics <- is_graphics [keep ]
140+ if (! (all(is_graphics ) || all(! is_graphics ))) {
141+ cli :: cli_warn(
142+ " Serializers are a mix of standard and graphics serializers"
143+ )
144+ }
145+ serializers
123146}
124147
125148get_serializers_internal <- function (
126149 types = NULL ,
127150 env = caller_env(),
128- dots_serializers = NULL
151+ dots_serializers = NULL ,
152+ prune_dots = TRUE
129153) {
130154 if (isTRUE(tolower(types ) == " none" )) {
131155 return (NULL )
@@ -134,10 +158,19 @@ get_serializers_internal <- function(
134158 types <- names(registry $ serializers )
135159 }
136160 dots <- which(types == " ..." )
161+ from_dots <- rep_along(types , FALSE )
137162 if (length(dots ) != 0 ) {
163+ if (length(dots ) > 1 ) {
164+ cli :: cli_abort(" {.val ...} can only be used once" )
165+ }
166+ dnames <- dots_serializers %|| % setdiff(names(registry $ serializers ), types )
167+ from_dots <- rep(
168+ c(FALSE , TRUE , FALSE ),
169+ c(dots - 1 , length(dnames ), length(types ) - dots )
170+ )
138171 types <- c(
139172 types [seq_len(dots - 1 )],
140- dots_serializers % || % setdiff(names( registry $ serializers ), types ) ,
173+ dnames ,
141174 types [dots + seq_len(length(types ) - dots )]
142175 )
143176 }
@@ -177,7 +210,22 @@ get_serializers_internal <- function(
177210 lapply(serializers , `[[` , " fun" ),
178211 vapply(serializers , `[[` , character (1 ), " type" )
179212 )
180- serializers [! duplicated(names(serializers ))]
213+ is_graphics <- vapply(serializers , is_device_formatter , logical (1 ))
214+ if (prune_dots ) {
215+ use_graphics <- if (all(from_dots )) FALSE else is_graphics [! from_dots ][1 ]
216+ keep <- ! from_dots | is_graphics == use_graphics
217+ serializers <- serializers [keep ]
218+ is_graphics <- is_graphics [keep ]
219+ if (! (all(is_graphics ) || all(! is_graphics ))) {
220+ cli :: cli_warn(
221+ " Serializers are a mix of standard and graphics serializers"
222+ )
223+ }
224+ }
225+ structure(
226+ serializers [! duplicated(names(serializers ))],
227+ from_dots = from_dots [! duplicated(names(serializers ))]
228+ )
181229}
182230
183231# Default serializers ----------------------------------------------------------
@@ -195,25 +243,25 @@ get_serializers_internal <- function(
195243# ' as `"tsv"` to the mime type `text/tsv`
196244# ' * `format_rds()` uses [serialize()] for formatting. It is registered as
197245# ' `"rds"` to the mime type `application/rds`
198- # ' * `format_geojson` uses [geojsonsf::sfc_geojson()] or [geojsonsf::sf_geojson()]
246+ # ' * `format_geojson()` uses [geojsonsf::sfc_geojson()] or [geojsonsf::sf_geojson()]
199247# ' for formatting depending on the class of the response body. It is
200248# ' registered as `"geojson"` to the mime type `application/geo+json`
201- # ' * `format_feather` uses [arrow::write_feather()] for formatting. It is
249+ # ' * `format_feather()` uses [arrow::write_feather()] for formatting. It is
202250# ' registered as `"feather"` to the mime type
203251# ' `application/vnd.apache.arrow.file`
204- # ' * `format_parquet` uses [nanoparquet::write_parquet()] for formatting. It is
252+ # ' * `format_parquet()` uses [nanoparquet::write_parquet()] for formatting. It is
205253# ' registered as `"parquet"` to the mime type `application/vnd.apache.parquet`
206- # ' * `format_yaml` uses [yaml::as.yaml()] for formatting. It is registered
254+ # ' * `format_yaml()` uses [yaml::as.yaml()] for formatting. It is registered
207255# ' as `"yaml"` to the mime type `text/yaml`
208- # ' * `format_htmlwidget` uses [htmlwidgets::saveWidget()] for formatting. It is
256+ # ' * `format_htmlwidget()` uses [htmlwidgets::saveWidget()] for formatting. It is
209257# ' registered as `"htmlwidget"` to the mime type `text/html`
210- # ' * `format_format` uses [format()] for formatting. It is registered
258+ # ' * `format_format()` uses [format()] for formatting. It is registered
211259# ' as `"format"` to the mime type `text/plain`
212- # ' * `format_print` uses [print()] for formatting. It is registered
260+ # ' * `format_print()` uses [print()] for formatting. It is registered
213261# ' as `"print"` to the mime type `text/plain`
214- # ' * `format_cat` uses [cat()] for formatting. It is registered
262+ # ' * `format_cat()` uses [cat()] for formatting. It is registered
215263# ' as `"cat"` to the mime type `text/plain`
216- # ' * `format_unboxed` uses [reqres::format_json()] with `auto_unbox = TRUE` for
264+ # ' * `format_unboxed()` uses [reqres::format_json()] with `auto_unbox = TRUE` for
217265# ' formatting. It is registered as `"unboxedJSON"` to the mime type
218266# ' `application/json`
219267# '
@@ -227,6 +275,25 @@ get_serializers_internal <- function(
227275# ' * [reqres::format_plain()] is registered as "`text`" to the mime type
228276# ' `text/plain`
229277# '
278+ # ' # Provided graphics serializers
279+ # ' Serializing graphic output is special because it requires operations before
280+ # ' and after the handler is executed. Further, handlers creating graphics are
281+ # ' expected to do so through side-effects (ie. call to graphics rendering) or
282+ # ' by returning a ggplot2 object. If you want to create your own graphics
283+ # ' serializer you should use [device_formatter()] for constructing it.
284+ # ' * `format_png()` uses [ragg::agg_png()] for rendering. It is registered
285+ # ' as `"png"` to the mime type `image/png`
286+ # ' * `format_jpeg()` uses [ragg::agg_jpeg()] for rendering. It is registered
287+ # ' as `"jpeg"` to the mime type `image/jpeg`
288+ # ' * `format_tiff()` uses [ragg::agg_tiff()] for rendering. It is registered
289+ # ' as `"tiff"` to the mime type `image/tiff`
290+ # ' * `format_svg()` uses [svglite::svglite()] for rendering. It is registered
291+ # ' as `"svg"` to the mime type `image/svg+xml`
292+ # ' * `format_bmp()` uses [grDevices::bmp()] for rendering. It is registered
293+ # ' as `"bmp"` to the mime type `image/bmp`
294+ # ' * `format_pdf()` uses [grDevices::pdf()] for rendering. It is registered
295+ # ' as `"pdf"` to the mime type `application/pdf`
296+ # '
230297# ' @param ... Further argument passed on to the internal formatting function.
231298# ' See Details for information on which function handles the formatting
232299# ' internally in each serializer
@@ -392,10 +459,19 @@ on_load({
392459
393460# Device serializers -----------------------------------------------------------
394461
395- # TODO: Somehow, we need to keep these distinct from the other serializers so
396- # that ... when used together with a device serializer only retrieves other
397- # device serializers and vice versa
398-
462+ # ' Create a graphics device formatter
463+ # '
464+ # ' This internal function facilitates creating a formatter that uses a specific
465+ # ' device for rendering.
466+ # '
467+ # ' @param dev_open The function that opens the device
468+ # ' @param dev_close The function closing the device. Usually this would be
469+ # ' [grDevices::dev.off()]
470+ # '
471+ # ' @return A device formatter function
472+ # ' @keywords internal
473+ # ' @export
474+ # '
399475device_formatter <- function (dev_open , dev_close = grDevices :: dev.off()) {
400476 dev_name <- caller_arg(dev_open )
401477 check_function(dev_open )
@@ -408,50 +484,54 @@ device_formatter <- function(dev_open, dev_close = grDevices::dev.off()) {
408484 )
409485 }
410486 }
411- function (... ) {
412- provided_args <- names(enquos(... ))
413- dev_args <- fn_fmls_names(dev_open )
414- extra_args <- setdiff(provided_args , dev_args )
415- if (length(extra_args ) != 0 && ! " ..." %in% dev_args ) {
416- cli :: cli_abort(
417- " Provided arguments does not match arguments in {.fun {dev_name}}"
418- )
419- }
420- init_dev <- function () {
421- output_file <- tempfile()
422- dev_open(filename = output_file , ... )
423- dev_id <- grDevices :: dev.cur()
424- list (path = output_file , dev = dev_id )
425- }
426- close_dev <- function (info ) {
427- grDevices :: dev.set(info $ dev )
428- grDevices :: dev.off()
429- if (! file.exists(info $ path )) {
430- return (NULL )
487+ structure(
488+ function (... ) {
489+ provided_args <- names(enquos(... ))
490+ dev_args <- fn_fmls_names(dev_open )
491+ extra_args <- setdiff(provided_args , dev_args )
492+ if (length(extra_args ) != 0 && ! " ..." %in% dev_args ) {
493+ cli :: cli_abort(
494+ " Provided arguments does not match arguments in {.fun {dev_name}}"
495+ )
496+ }
497+ init_dev <- function () {
498+ output_file <- tempfile()
499+ dev_open(filename = output_file , ... )
500+ dev_id <- grDevices :: dev.cur()
501+ list (path = output_file , dev = dev_id )
431502 }
432- con <- file(info $ path , " rb" )
433- on.exit(
434- {
435- close(con )
436- unlink(info $ path )
437- },
438- add = TRUE
503+ close_dev <- function (info ) {
504+ grDevices :: dev.set(info $ dev )
505+ grDevices :: dev.off()
506+ if (! file.exists(info $ path )) {
507+ return (NULL )
508+ }
509+ con <- file(info $ path , " rb" )
510+ on.exit(
511+ {
512+ close(con )
513+ unlink(info $ path )
514+ },
515+ add = TRUE
516+ )
517+ readBin(con , " raw" , file.info(info $ path )$ size )
518+ }
519+ clean_dev <- function (info ) {
520+ grDevices :: dev.set(info $ dev )
521+ grDevices :: dev.off()
522+ unlink(info $ path )
523+ }
524+ structure(
525+ identity ,
526+ init = init_dev ,
527+ close = close_dev ,
528+ clean = clean_dev ,
529+ class = " device_formatter"
439530 )
440- readBin(con , " raw" , file.info(info $ path )$ size )
441- }
442- clean_dev <- function (info ) {
443- grDevices :: dev.set(info $ dev )
444- grDevices :: dev.off()
445- unlink(info $ path )
446531 }
447- structure(
448- identity ,
449- init = init_dev ,
450- close = close_dev ,
451- clean = clean_dev
452- )
453- }
532+ )
454533}
534+ is_device_formatter <- function (x ) inherits(x , " device_formatter" )
455535
456536init_formatter <- function (formatter ) {
457537 init_fun <- attr(formatter , " init" )
@@ -477,22 +557,40 @@ clean_formatter <- function(formatter, info) {
477557 clean_fun(info )
478558}
479559
560+ # ' @rdname serializers
561+ # ' @export
480562# ' @importFrom ragg agg_png
563+ # '
481564format_png <- device_formatter(agg_png )
565+ # ' @rdname serializers
566+ # ' @export
482567# ' @importFrom ragg agg_jpeg
568+ # '
483569format_jpeg <- device_formatter(agg_jpeg )
570+ # ' @rdname serializers
571+ # ' @export
484572# ' @importFrom ragg agg_tiff
573+ # '
485574format_tiff <- device_formatter(agg_tiff )
575+ # ' @rdname serializers
576+ # ' @export
486577# ' @importFrom svglite svglite
578+ # '
487579format_svg <- device_formatter(svglite )
580+ # ' @rdname serializers
581+ # ' @export
582+ # '
488583format_bmp <- device_formatter(grDevices :: bmp )
584+ # ' @rdname serializers
585+ # ' @export
586+ # '
489587format_pdf <- device_formatter(grDevices :: pdf )
490588
491589on_load({
492590 register_serializer(" png" , format_png , " image/png" )
493591 register_serializer(" jpeg" , format_jpeg , " image/jpeg" )
494592 register_serializer(" tiff" , format_tiff , " image/tiff" )
495593 register_serializer(" svg" , format_svg , " image/svg+xml" )
496- register_serializer(" bmp" , format_bmp , " aimage /bmp" )
594+ register_serializer(" bmp" , format_bmp , " image /bmp" )
497595 register_serializer(" pdf" , format_pdf , " application/pdf" )
498596})
0 commit comments