From 89bb683f47982ebfeacae8ebce208393fc4a5e1a Mon Sep 17 00:00:00 2001 From: Sebastian Gatscha Date: Thu, 28 Nov 2024 14:26:49 +0100 Subject: [PATCH 1/4] fix: respect all highlightOptions with updateStyleFromProperties and shallow copy of style --- inst/htmlwidgets/lib/FlatGeoBuf/fgb.js | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/inst/htmlwidgets/lib/FlatGeoBuf/fgb.js b/inst/htmlwidgets/lib/FlatGeoBuf/fgb.js index 17a0633..7556771 100644 --- a/inst/htmlwidgets/lib/FlatGeoBuf/fgb.js +++ b/inst/htmlwidgets/lib/FlatGeoBuf/fgb.js @@ -146,11 +146,9 @@ LeafletWidget.methods.addFlatGeoBuf = function (layerId, }, // remove highlight when hover stops 'mouseout': function(e) { - const layer = e.target; - if (e.layer.feature.properties.color) { - style.color = e.layer.feature.properties.color - } - layer.setStyle(style); + const layer = e.layer; + let oldstyle = updateStyleFromProperties(structuredClone(style), layer.feature.properties); + layer.setStyle(oldstyle); if (highlightOptions.sendToBack) { layer.bringToBack(); } @@ -221,7 +219,6 @@ function makePopup(popup, className) { return pop; } - function json2table(json, cls) { let cols = Object.keys(json); let vals = Object.values(json); @@ -286,6 +283,18 @@ function updateStyle(style_obj, feature, scale, scaleValues) { return out; } +function updateStyleFromProperties(style, props) { + const keysToUpdate = ['stroke', 'color', 'weight', 'opacity', + 'fill', 'fillColor', 'fillOpacity', 'dashArray']; + // Create a shallow copy of style to avoid mutating the original object + const updatedStyle = { ...style }; + keysToUpdate.forEach(key => { + if (updatedStyle[key] === null && props[key]) { + updatedStyle[key] = props[key]; + } + }); + return updatedStyle +} function rescale(value, to_min, to_max, from_min, from_max) { @@ -297,6 +306,7 @@ function rescale(value, to_min, to_max, from_min, from_max) { + LeafletWidget.methods.addFlatGeoBufFiltered = function (layerId, group, url, From ffbbf83d4948b9bdd1108b6959ca6d05602919dd Mon Sep 17 00:00:00 2001 From: Sebastian Gatscha Date: Thu, 28 Nov 2024 14:27:36 +0100 Subject: [PATCH 2/4] docs: add FGB styling details, simplify dependency calls --- R/file.R | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/R/file.R b/R/file.R index fab7d28..55a4db9 100644 --- a/R/file.R +++ b/R/file.R @@ -152,17 +152,13 @@ addLocalFile = function(map, map$dependencies <- c( map$dependencies, + leafletFileDependencies(), fileDependency( fn = path_outfile, layerId = layerId ) ) - map$dependencies <- c( - map$dependencies, - leafletFileDependencies() - ) - leaflet::invokeMethod( map, leaflet::getMapData(map), @@ -308,6 +304,25 @@ addTileFolder = function(map, #' @inheritParams leaflet::addPolylines #' @param ... currently not used. #' +#' @details +#' Styling options in `addFgb` offer flexibility by allowing +#' users to either specify styles directly as function arguments or define them +#' as attributes in the data object: +#' +#' - **Direct Styling:** You can pass style arguments (e.g., `color`, `weight`, +#' `opacity`) directly to the function. These will apply uniformly to all features +#' in the layer. +#' - **Attribute-based Styling:** Alternatively, you can include styling properties +#' (e.g., `color`, `fillColor`, `weight`) as columns in your data object before +#' writing it to an FGB file. Set the corresponding arguments in `addFgb` to +#' `NULL`, and the function will use these attributes for styling during map +#' rendering. For example: +#' ```R +#' data$color <- colorNumeric(palette = "viridis", domain = data$var)(data$var) +#' sf::st_write(obj = data, dsn = "myfile.fgb", driver = "FlatGeobuf") +#' leafem::addFgb(file = "myfile.fgb", color = NULL) +#' ``` +#' #' @examples #' if (interactive()) { #' library(leaflet) @@ -414,10 +429,6 @@ addFgb = function(map, map$dependencies , fgbDependencies() , chromaJsDependencies() - ) - - map$dependencies = c( - map$dependencies , fileAttachment(path_layer, group) ) From f55e89addcbfa3ec985b6d19b4f6032184ebb9c4 Mon Sep 17 00:00:00 2001 From: Sebastian Gatscha Date: Thu, 28 Nov 2024 14:29:29 +0100 Subject: [PATCH 3/4] docs: add Roxygen: list(markdown = TRUE) redocumented --- DESCRIPTION | 1 + leafem.Rproj | 3 ++- man/addCOG.Rd | 4 +-- man/addFgb.Rd | 44 +++++++++++++++++++++++--------- man/addLogo.Rd | 4 +-- man/addMouseCoordinates.Rd | 14 +++++----- man/addPMPolygons.Rd | 12 ++++----- man/addRasterRGB.Rd | 8 +++--- man/addReactiveFeatures.Rd | 16 ++++++------ man/addTileFolder.Rd | 2 +- man/extendLayersControl.Rd | 52 ++++++++++++++++++++------------------ man/imagequeryOptions.Rd | 6 ++--- man/paintRules.Rd | 4 +-- 13 files changed, 97 insertions(+), 73 deletions(-) diff --git a/DESCRIPTION b/DESCRIPTION index 3c3d9ab..419552c 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -91,4 +91,5 @@ Suggests: terra, tools Encoding: UTF-8 +Roxygen: list(markdown = TRUE) RoxygenNote: 7.3.2 diff --git a/leafem.Rproj b/leafem.Rproj index db5c8db..20c5816 100644 --- a/leafem.Rproj +++ b/leafem.Rproj @@ -1,5 +1,4 @@ Version: 1.0 -ProjectId: a2cce78f-b118-47fb-83fb-d62c6c6506d5 RestoreWorkspace: Default SaveWorkspace: Default @@ -16,6 +15,8 @@ LaTeX: pdfLaTeX StripTrailingWhitespace: Yes BuildType: Package +PackageUseDevtools: Yes +PackageCleanBeforeInstall: No PackageInstallArgs: --no-multiarch --with-keep.source PackageBuildArgs: --no-manual PackageCheckArgs: --as-cran --no-manual diff --git a/man/addCOG.Rd b/man/addCOG.Rd index 072365f..f3d2ef7 100644 --- a/man/addCOG.Rd +++ b/man/addCOG.Rd @@ -67,8 +67,8 @@ Add Cloud Optimised Geotiff (COG) to a leaflet map. } \details{ This function will overlay Cloud Optimised Geotiff data from a remote url on -a leaflet map. Like `addGeotiff` it uses the leaflet plugin -'georaster-layer-for-leaflet' to render the data. See `addGeotiff` for a bit +a leaflet map. Like \code{addGeotiff} it uses the leaflet plugin +'georaster-layer-for-leaflet' to render the data. See \code{addGeotiff} for a bit more detail what that means. } \examples{ diff --git a/man/addFgb.Rd b/man/addFgb.Rd index 4346c66..86728c4 100644 --- a/man/addFgb.Rd +++ b/man/addFgb.Rd @@ -91,18 +91,38 @@ options for each label. Default \code{NULL}} } \description{ flatgeobuf is a performant binary geo-spatial file format suitable for - serving large data. For more details see - \url{https://github.com/flatgeobuf/flatgeobuf} and the respective - documentation for the GDAL/OGR driver at - \url{https://gdal.org/en/latest/drivers/vector/flatgeobuf.html}. \cr - \cr - In contrast to classical ways of serving data from R onto a leaflet map, - flatgeobuf can stream the data chunk by chunk so that rendering of the map - is more or less instantaneous. The map is responsive while data is still - loading so that popup queries, zooming and panning will work even - though not all data has been rendered yet. This makes for a rather pleasant - user experience as we don't have to wait for all data to be added to the map - before interacting with it. +serving large data. For more details see +\url{https://github.com/flatgeobuf/flatgeobuf} and the respective +documentation for the GDAL/OGR driver at +\url{https://gdal.org/en/latest/drivers/vector/flatgeobuf.html}. \cr +\cr +In contrast to classical ways of serving data from R onto a leaflet map, +flatgeobuf can stream the data chunk by chunk so that rendering of the map +is more or less instantaneous. The map is responsive while data is still +loading so that popup queries, zooming and panning will work even +though not all data has been rendered yet. This makes for a rather pleasant +user experience as we don't have to wait for all data to be added to the map +before interacting with it. +} +\details{ +Styling options in \code{addFgb} offer flexibility by allowing +users to either specify styles directly as function arguments or define them +as attributes in the data object: +\itemize{ +\item \strong{Direct Styling:} You can pass style arguments (e.g., \code{color}, \code{weight}, +\code{opacity}) directly to the function. These will apply uniformly to all features +in the layer. +\item \strong{Attribute-based Styling:} Alternatively, you can include styling properties +(e.g., \code{color}, \code{fillColor}, \code{weight}) as columns in your data object before +writing it to an FGB file. Set the corresponding arguments in \code{addFgb} to +\code{NULL}, and the function will use these attributes for styling during map +rendering. For example: + +\if{html}{\out{
}}\preformatted{data$color <- colorNumeric(palette = "viridis", domain = data$var)(data$var) +sf::st_write(obj = data, dsn = "myfile.fgb", driver = "FlatGeobuf") +leafem::addFgb(file = "myfile.fgb", color = NULL) +}\if{html}{\out{
}} +} } \examples{ if (interactive()) { diff --git a/man/addLogo.Rd b/man/addLogo.Rd index 5816eed..6b49812 100644 --- a/man/addLogo.Rd +++ b/man/addLogo.Rd @@ -38,8 +38,8 @@ showLogo(map, layerId) \item{alpha}{opacity of the added image.} -\item{src}{DEPRECATED. The function now automatically determines if `img` is -a local or remote image using `file.exists(img)`.} +\item{src}{DEPRECATED. The function now automatically determines if \code{img} is +a local or remote image using \code{file.exists(img)}.} \item{url}{an optional URL to be opened when clicking on the image (e.g. company's homepage).} diff --git a/man/addMouseCoordinates.Rd b/man/addMouseCoordinates.Rd index 15ab843..95a1604 100644 --- a/man/addMouseCoordinates.Rd +++ b/man/addMouseCoordinates.Rd @@ -47,13 +47,13 @@ on the map. \details{ If style is set to "detailed", the following information will be displayed: \itemize{ - \item x: x-position of the mouse cursor in projected coordinates - \item y: y-position of the mouse cursor in projected coordinates - \item epsg: the epsg code of the coordinate reference system of the map - \item proj4: the proj4 definition of the coordinate reference system of the map - \item lat: latitude position of the mouse cursor - \item lon: longitude position of the mouse cursor - \item zoom: the current zoom level +\item x: x-position of the mouse cursor in projected coordinates +\item y: y-position of the mouse cursor in projected coordinates +\item epsg: the epsg code of the coordinate reference system of the map +\item proj4: the proj4 definition of the coordinate reference system of the map +\item lat: latitude position of the mouse cursor +\item lon: longitude position of the mouse cursor +\item zoom: the current zoom level } By default, only 'lat', 'lon' and 'zoom' are shown. To show the details about diff --git a/man/addPMPolygons.Rd b/man/addPMPolygons.Rd index ad1cfd0..95f7e92 100644 --- a/man/addPMPolygons.Rd +++ b/man/addPMPolygons.Rd @@ -48,7 +48,7 @@ addPMPolylines( \item{group}{group name.} \item{pane}{the map pane to which the layer should be added. See -[leaflet](addMapPane) for details.} +\href{addMapPane}{leaflet} for details.} \item{attribution}{optional attribution character string.} } @@ -61,12 +61,12 @@ Add polylines stored as PMTiles } \details{ These functions can be used to add cloud optimized vector tiles data in - the `.pmtiles` format stored in an Amazon Web Services (AWS) S3 bucket to a - leaflet map. For instructions on how to create these files, see - \url{https://github.com/protomaps/PMTiles}. +the \code{.pmtiles} format stored in an Amazon Web Services (AWS) S3 bucket to a +leaflet map. For instructions on how to create these files, see +\url{https://github.com/protomaps/PMTiles}. - NOTE: You may not see the tiles rendered in the RStudio viewer pane. Make - sure to open the map in a browser. +NOTE: You may not see the tiles rendered in the RStudio viewer pane. Make +sure to open the map in a browser. } \section{Functions}{ \itemize{ diff --git a/man/addRasterRGB.Rd b/man/addRasterRGB.Rd index 8045ed9..14d4749 100644 --- a/man/addRasterRGB.Rd +++ b/man/addRasterRGB.Rd @@ -34,7 +34,7 @@ addStarsRGB( \arguments{ \item{map}{a map widget object created from `leaflet()``} -\item{x}{a `RasterBrick`, `RasterStack` or `stars`` raster object} +\item{x}{a \code{RasterBrick}, \code{RasterStack} or `stars`` raster object} \item{r}{integer. Index of the Red channel/band, between 1 and nlayers(x)} @@ -43,11 +43,11 @@ addStarsRGB( \item{b}{integer. Index of the Blue channel/band, between 1 and nlayers(x)} \item{quantiles}{the upper and lower quantiles used for color stretching. -If set to NULL, stretching is performed basing on `domain` argument.} +If set to NULL, stretching is performed basing on \code{domain} argument.} \item{domain}{the upper and lower values used for color stretching. -This is used only if `quantiles` is NULL. -If both `domain` and `quantiles` are set to NULL, stretching is applied +This is used only if \code{quantiles} is NULL. +If both \code{domain} and \code{quantiles} are set to NULL, stretching is applied based on min-max values.} \item{na.color}{the color to be used for NA pixels} diff --git a/man/addReactiveFeatures.Rd b/man/addReactiveFeatures.Rd index f3c904b..951a187 100644 --- a/man/addReactiveFeatures.Rd +++ b/man/addReactiveFeatures.Rd @@ -50,14 +50,14 @@ See \code{\link[leaflet]{addControl}} for details.} } \description{ This function adds a layer to a map that is dependent on another layer. - The reactive layer will be shown/hidden when holding the Ctrl-button on your - keyboard and performing the action defined by \code{on}. \code{on} can be - either "click" (default) or "mouseover". - - Note: \code{srcLayer} needs to be added to the map using \code{\link[leaflet]{addGeoJSON}} - because we need to be able to link the two layers by a common attribute - defined by argument \code{by}. Linking will be done via \code{group} name - of \code{srcLayer}. +The reactive layer will be shown/hidden when holding the Ctrl-button on your +keyboard and performing the action defined by \code{on}. \code{on} can be +either "click" (default) or "mouseover". + +Note: \code{srcLayer} needs to be added to the map using \code{\link[leaflet]{addGeoJSON}} +because we need to be able to link the two layers by a common attribute +defined by argument \code{by}. Linking will be done via \code{group} name +of \code{srcLayer}. } \examples{ library(leaflet) diff --git a/man/addTileFolder.Rd b/man/addTileFolder.Rd index 4c308d7..2193da0 100644 --- a/man/addTileFolder.Rd +++ b/man/addTileFolder.Rd @@ -39,5 +39,5 @@ but can be overridden.} } \description{ Add tiled raster data pyramids from a local folder that was created with - gdal2tiles.py (see \url{https://gdal.org/en/latest/programs/gdal2tiles.html} for details). +gdal2tiles.py (see \url{https://gdal.org/en/latest/programs/gdal2tiles.html} for details). } diff --git a/man/extendLayersControl.Rd b/man/extendLayersControl.Rd index a67efa6..d16b0d9 100644 --- a/man/extendLayersControl.Rd +++ b/man/extendLayersControl.Rd @@ -15,55 +15,57 @@ extendLayersControl( ) } \arguments{ -\item{map}{A `leaflet` or `mapview` object to which the extended layers control will be added.} +\item{map}{A \code{leaflet} or \code{mapview} object to which the extended layers control will be added.} \item{view_settings}{A list specifying the view settings for each layer. Each list element should contain either: \itemize{ - \item \code{coords}: A vector of length 2 (latitude, longitude) for setting the view, or length 4 - (bounding box: lat1, lng1, lat2, lng2) for fitting the bounds. - \item \code{zoom}: The zoom level (used for `setView`). - \item \code{fly} (optional): A logical indicating whether to use `flyTo` or `flyToBounds` instead of `setView` or `fitBounds`. - \item \code{options} (optional): Additional options to pass to `setView`, `fitBounds`, or `flyTo`. +\item \code{coords}: A vector of length 2 (latitude, longitude) for setting the view, or length 4 +(bounding box: lat1, lng1, lat2, lng2) for fitting the bounds. +\item \code{zoom}: The zoom level (used for \code{setView}). +\item \code{fly} (optional): A logical indicating whether to use \code{flyTo} or \code{flyToBounds} instead of \code{setView} or \code{fitBounds}. +\item \code{options} (optional): Additional options to pass to \code{setView}, \code{fitBounds}, or \code{flyTo}. }} -\item{home_btns}{Logical. If `TRUE`, adds a "home" button next to each layer name in the layer control. +\item{home_btns}{Logical. If \code{TRUE}, adds a "home" button next to each layer name in the layer control. Clicking the home button zooms the map to the view specified for that layer in \code{view_settings}.} \item{home_btn_options}{A list of options to customize the home button appearance and behavior. Possible options include: -- `text`: The text or emoji to display on the button (default is '🏠'). -- `cursor`: CSS cursor style for the button (default is 'pointer'). -- `class`: CSS class name for the button (default is 'leaflet-home-btn'). -- `styles`: Semicolon separated CSS-string (default is 'float: inline-end;').} +\itemize{ +\item \code{text}: The text or emoji to display on the button (default is '🏠'). +\item \code{cursor}: CSS cursor style for the button (default is 'pointer'). +\item \code{class}: CSS class name for the button (default is 'leaflet-home-btn'). +\item \code{styles}: Semicolon separated CSS-string (default is 'float: inline-end;'). +}} -\item{setviewonselect}{Logical. If `TRUE` (default) sets the view when the layer is selected.} +\item{setviewonselect}{Logical. If \code{TRUE} (default) sets the view when the layer is selected.} \item{opacityControl}{A list specifying the opacity control settings for each layer. Each list element should contain: \itemize{ - \item \code{min}: Minimum opacity value (default is 0). - \item \code{max}: Maximum opacity value (default is 1). - \item \code{step}: Step size for the opacity slider (default is 0.1). - \item \code{default}: Default opacity value (default is 1). - \item \code{width}: Width of the opacity slider (default is '100%'). - \item \code{class}: CSS class name for the slider (default is 'leaflet-opacity-slider'). +\item \code{min}: Minimum opacity value (default is 0). +\item \code{max}: Maximum opacity value (default is 1). +\item \code{step}: Step size for the opacity slider (default is 0.1). +\item \code{default}: Default opacity value (default is 1). +\item \code{width}: Width of the opacity slider (default is '100\%'). +\item \code{class}: CSS class name for the slider (default is 'leaflet-opacity-slider'). }} -\item{includelegends}{Logical. If `TRUE` (default), appends legends to the layer control. Legends are matched +\item{includelegends}{Logical. If \code{TRUE} (default), appends legends to the layer control. Legends are matched to layers by their group name. The legends need to be added with corresponding layer IDs.} } \value{ -A modified `leaflet` map object with extended layers control including view controls, home buttons, opacity controls, and legends. +A modified \code{leaflet} map object with extended layers control including view controls, home buttons, opacity controls, and legends. } \description{ -This function extends an existing layers control in a `leaflet` map by adding custom views, home buttons, -opacity controls, and legends. It enhances the functionality of a layers control created with `leaflet` -or `leaflet.extras`. +This function extends an existing layers control in a \code{leaflet} map by adding custom views, home buttons, +opacity controls, and legends. It enhances the functionality of a layers control created with \code{leaflet} +or \code{leaflet.extras}. } \details{ -This function generates JavaScript that listens for `overlayadd` or `baselayerchange` events +This function generates JavaScript that listens for \code{overlayadd} or \code{baselayerchange} events and automatically sets the view or zoom level according to the specified \code{view_settings}. -If `home_btns` is enabled, a home button is added next to each layer in the layer control. +If \code{home_btns} is enabled, a home button is added next to each layer in the layer control. When clicked, it zooms the map to the predefined view of that layer. The opacity control slider allows users to adjust the opacity of layers. The legend will be appended to the corresponding layer control, matched by the layer's group name. diff --git a/man/imagequeryOptions.Rd b/man/imagequeryOptions.Rd index 004982e..1c64e06 100644 --- a/man/imagequeryOptions.Rd +++ b/man/imagequeryOptions.Rd @@ -25,9 +25,9 @@ to 'mousemove'.} \item{prefix}{a character string to be shown as prefix for the layerId.} -\item{noData}{the text shown when the mouse is over a `NoData Value` as -defined by GDAL. The default "NoData Value" will use whatever is defined in the -Geotif.} +\item{noData}{the text shown when the mouse is over a \verb{NoData Value} as +identified by GDAL. The default "NoData Value" will show whatever is defined by +the Geotiff metadata.} } \description{ Imagequery options for addGeoRaster, addGeotiff and addCOG diff --git a/man/paintRules.Rd b/man/paintRules.Rd index 2abe5e4..8c7f4e8 100644 --- a/man/paintRules.Rd +++ b/man/paintRules.Rd @@ -33,9 +33,9 @@ paintRules( \item{opacity}{point opacity} -\item{dash}{either `NULL` (default) for a solid line or a numeric vector +\item{dash}{either \code{NULL} (default) for a solid line or a numeric vector of length 2 denoting segment length and spce between segments (in pixels), -e.g. `c(5, 3)`} +e.g. \code{c(5, 3)}} } \description{ Styling options for PMTiles From b1041c7f40ac1905dd686a745c56eac291617786 Mon Sep 17 00:00:00 2001 From: Sebastian Gatscha Date: Thu, 12 Dec 2024 12:06:17 +0100 Subject: [PATCH 4/4] docs: add fgb example for fillColor --- R/file.R | 10 +++++++++- man/addFgb.Rd | 12 ++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/R/file.R b/R/file.R index 55a4db9..d952cc1 100644 --- a/R/file.R +++ b/R/file.R @@ -316,11 +316,19 @@ addTileFolder = function(map, #' (e.g., `color`, `fillColor`, `weight`) as columns in your data object before #' writing it to an FGB file. Set the corresponding arguments in `addFgb` to #' `NULL`, and the function will use these attributes for styling during map -#' rendering. For example: +#' rendering. +#' +#' For example: #' ```R +#' ## using custom `color` #' data$color <- colorNumeric(palette = "viridis", domain = data$var)(data$var) #' sf::st_write(obj = data, dsn = "myfile.fgb", driver = "FlatGeobuf") #' leafem::addFgb(file = "myfile.fgb", color = NULL) +#' +#' ## using custom `fillColor` +#' data$fillColor <- colorNumeric(palette = "viridis", domain = data$var)(data$var) +#' sf::st_write(obj = data, dsn = "myfile.fgb", driver = "FlatGeobuf") +#' leafem::addFgb(file = "myfile.fgb", fill = TRUE, fillColor = NULL) #' ``` #' #' @examples diff --git a/man/addFgb.Rd b/man/addFgb.Rd index 86728c4..84d3113 100644 --- a/man/addFgb.Rd +++ b/man/addFgb.Rd @@ -116,11 +116,19 @@ in the layer. (e.g., \code{color}, \code{fillColor}, \code{weight}) as columns in your data object before writing it to an FGB file. Set the corresponding arguments in \code{addFgb} to \code{NULL}, and the function will use these attributes for styling during map -rendering. For example: +rendering. -\if{html}{\out{
}}\preformatted{data$color <- colorNumeric(palette = "viridis", domain = data$var)(data$var) +For example: + +\if{html}{\out{
}}\preformatted{## using custom `color` +data$color <- colorNumeric(palette = "viridis", domain = data$var)(data$var) sf::st_write(obj = data, dsn = "myfile.fgb", driver = "FlatGeobuf") leafem::addFgb(file = "myfile.fgb", color = NULL) + +## using custom `fillColor` +data$fillColor <- colorNumeric(palette = "viridis", domain = data$var)(data$var) +sf::st_write(obj = data, dsn = "myfile.fgb", driver = "FlatGeobuf") +leafem::addFgb(file = "myfile.fgb", fill = TRUE, fillColor = NULL) }\if{html}{\out{
}} } }