Author: Gabriel Becker
Date: 2025-12-06

# Introduction to the formatters package

#### Gabriel Becker

#### 2025-12-06

    ##
    ## Attaching package: 'formatters'

    ## The following object is masked from 'package:base':
    ##
    ##     %||%

# formatters

The `formatters` package provides two core pieces of functionality, both related to ASCII rendering:

1. `format_value` provides the ability to format single- and multi-valued elements into ASCII display-ready strings
2. the `matrix_form` framework provides generics for implementing ASCII rendering support for display tables

Both of these feature sets are used in the `rtables` package.

# Formatting values

The core motivation for `formatters` is the rendering of reporting tables into ASCII. In this context a ‘value’ is the raw content that to appear in a single table cell. Most commonly this is a numeric vector of length 1, 2 or – occasionally – 3.

## Format labels

`formatters` ships with a large number of pre-defined formats appropriate for rendering values into ASCII strings. These existing formats are specified by their labels. We can see the list of these by calling the `list_valid_format_labels` function:

    list_valid_format_labels()

    $`1d`
     [1] "xx"                 "xx."                "xx.x"
     [4] "xx.xx"              "xx.xxx"             "xx.xxxx"
     [7] "xx%"                "xx.%"               "xx.x%"
    [10] "xx.xx%"             "xx.xxx%"            "(N=xx)"
    [13] "N=xx"               ">999.9"             ">999.99"
    [16] "x.xxxx | (<0.0001)" "default"

    $`2d`
     [1] "xx / xx"            "xx. / xx."          "xx.x / xx.x"
     [4] "xx.xx / xx.xx"      "xx.xxx / xx.xxx"    "N=xx (xx%)"
     [7] "xx (xx%)"           "xx (xx.%)"          "xx (xx.x%)"
    [10] "xx (xx.xx%)"        "xx. (xx.%)"         "xx.x (xx.x%)"
    [13] "xx.xx (xx.xx%)"     "(xx, xx)"           "(xx., xx.)"
    [16] "(xx.x, xx.x)"       "(xx.xx, xx.xx)"     "(xx.xxx, xx.xxx)"
    [19] "(xx.xxxx, xx.xxxx)" "xx - xx"            "xx.x - xx.x"
    [22] "xx.xx - xx.xx"      "xx (xx)"            "xx. (xx.)"
    [25] "xx.x (xx.x)"        "xx.xx (xx.xx)"      "xx (xx.)"
    [28] "xx (xx.x)"          "xx (xx.xx)"         "xx. (xx.x)"
    [31] "xx.x (xx.xx)"       "xx.xx (xx.xxx)"     "xx, xx"
    [34] "xx.x, xx.x"         "xx.xx, xx.xx"       "xx.x to xx.x"

    $`3d`
    [1] "xx. (xx. - xx.)"          "xx.x (xx.x - xx.x)"
    [3] "xx.xx (xx.xx - xx.xx)"    "xx.xxx (xx.xxx - xx.xxx)"
    [5] "xx / xx (xx.%)"           "xx / xx (xx.x%)"
    [7] "xx / xx (xx.xx%)"

    attr(,"info")
    [1] "xx does not modify the element, and xx. rounds a number to 0 digits"

Each of these labels describes how the incoming (possibly multi-element) raw value will be formatted. `xx` indicates that an element of the value will be printed as is, with no modification. `xx.` indicates that a numeric value element will be rounded to 0 decimal places, `xx.x` indicates rounding to 1 decimal place, etc.

## Formatting values

Values are formatted via calls to `format_value`, like so:

    format_value(5.1235, format = "xx.xx")

    [1] "5.12"

    format_value(c(1.2355, 2.6789), "(xx.xx, xx.xx)")

    [1] "(1.24, 2.68)"

# Table Rendering Framework

Advanced Usage Only These features are supported, and in fact are used in `rtables` and the experimental `rlistings`. That said, the API is currently very low-level and tailored to what `rtables` and `rlistings` need. How useful this is to other table frameworks may vary.

The second major piece of functionality in `formatters` is the ability to render tables into ASCII (and thus directly to the terminal) based on a so-called `MatrixPrintForm` representation of the table.

To hook up `rtables`-style ASCII display for your tables, it suffices to export a method for the exported `matrix_form` generic `formatters` provides. This method must return a `MatrixPrintForm` object representing your table.

We can build a baby example method for `data.frames` to illustrate this process:

    ## pagdfrow supports a large number of pieces of information regarding
    ## siblings and what information should be repeated after a pagination.
    ## we ignore all that here and just give the absolutely crucial info:
    ## nm (name), lab (label), rnum (absolute row position), pth ("path"),
    ## extent (how many lines it takes up), rclass ("class of row")
    fake_pagdf_row <- function(i, rnms) {
      nm <- rnms[i]
      pagdfrow(
        nm = nm, lab = nm, rnum = i, pth = nm, extent = 1L,
        rclass = "NA"
      )
    }

    matrix_form.data.frame <- function(df) {
      fmts <- lapply(df, function(x) if (is.null(obj_format(x))) "xx" else obj_format(x))

      bodystrs <- mapply(function(x, fmt) {
        sapply(x, format_value, format = fmt)
      }, x = df, fmt = fmts)

      rnms <- row.names(df)
      if (is.null(rnms)) {
        rnms <- as.character(seq_len(NROW(df)))
      }

      cnms <- names(df)

      strings <- rbind(
        c("", cnms),
        cbind(rnms, bodystrs)
      )

      fnr <- nrow(strings)
      fnc <- ncol(strings)

      ## center alignment for column labels, left alignment for everything else
      aligns <- rbind(
        "center",
        matrix("left", nrow = NROW(df), ncol = fnc)
      )

      ## build up fake pagination df,
      rowdf <- basic_pagdf(row.names(df))
      MatrixPrintForm(
        strings = strings,
        aligns = aligns,
        spans = matrix(1, nrow = fnr, ncol = fnc),
        formats = matrix("", nrow = fnr, ncol = fnc),
        row_info = rowdf,
        has_topleft = FALSE,
        nlines_header = 1,
        nrow_header = 1
      )
    }

    cat(toString(matrix_form.data.frame(mtcars)))

                          mpg    cyl   disp    hp    drat    wt     qsec    vs   am   gear   carb
    —————————————————————————————————————————————————————————————————————————————————————————————
    Mazda RX4             21     6     160     110   3.9    2.62    16.46   0    1    4      4
    Mazda RX4 Wag         21     6     160     110   3.9    2.875   17.02   0    1    4      4
    Datsun 710            22.8   4     108     93    3.85   2.32    18.61   1    1    4      1
    Hornet 4 Drive        21.4   6     258     110   3.08   3.215   19.44   1    0    3      1
    Hornet Sportabout     18.7   8     360     175   3.15   3.44    17.02   0    0    3      2
    Valiant               18.1   6     225     105   2.76   3.46    20.22   1    0    3      1
    Duster 360            14.3   8     360     245   3.21   3.57    15.84   0    0    3      4
    Merc 240D             24.4   4     146.7   62    3.69   3.19    20      1    0    4      2
    Merc 230              22.8   4     140.8   95    3.92   3.15    22.9    1    0    4      2
    Merc 280              19.2   6     167.6   123   3.92   3.44    18.3    1    0    4      4
    Merc 280C             17.8   6     167.6   123   3.92   3.44    18.9    1    0    4      4
    Merc 450SE            16.4   8     275.8   180   3.07   4.07    17.4    0    0    3      3
    Merc 450SL            17.3   8     275.8   180   3.07   3.73    17.6    0    0    3      3
    Merc 450SLC           15.2   8     275.8   180   3.07   3.78    18      0    0    3      3
    Cadillac Fleetwood    10.4   8     472     205   2.93   5.25    17.98   0    0    3      4
    Lincoln Continental   10.4   8     460     215   3      5.424   17.82   0    0    3      4
    Chrysler Imperial     14.7   8     440     230   3.23   5.345   17.42   0    0    3      4
    Fiat 128              32.4   4     78.7    66    4.08   2.2     19.47   1    1    4      1
    Honda Civic           30.4   4     75.7    52    4.93   1.615   18.52   1    1    4      2
    Toyota Corolla        33.9   4     71.1    65    4.22   1.835   19.9    1    1    4      1
    Toyota Corona         21.5   4     120.1   97    3.7    2.465   20.01   1    0    3      1
    Dodge Challenger      15.5   8     318     150   2.76   3.52    16.87   0    0    3      2
    AMC Javelin           15.2   8     304     150   3.15   3.435   17.3    0    0    3      2
    Camaro Z28            13.3   8     350     245   3.73   3.84    15.41   0    0    3      4
    Pontiac Firebird      19.2   8     400     175   3.08   3.845   17.05   0    0    3      2
    Fiat X1-9             27.3   4     79      66    4.08   1.935   18.9    1    1    4      1
    Porsche 914-2         26     4     120.3   91    4.43   2.14    16.7    0    1    5      2
    Lotus Europa          30.4   4     95.1    113   3.77   1.513   16.9    1    1    5      2
    Ford Pantera L        15.8   8     351     264   4.22   3.17    14.5    0    1    5      4
    Ferrari Dino          19.7   6     145     175   3.62   2.77    15.5    0    1    5      6
    Maserati Bora         15     8     301     335   3.54   3.57    14.6    0    1    5      8
    Volvo 142E            21.4   4     121     109   4.11   2.78    18.6    1    1    4      2