Introducing miniansi - a package for creating simple ANSI documents

miniansi

miniansi is a package for creating simple ANSI documents.

miniansi is an R package for creating coloured ANSI output.

miniansi currently has quite a minimal feature set, as only enough was included such that it could act as a graphics output device for R.

Installation

You can install from GitHub with:

# install.packages("remotes")
remotes::install_github("coolbutuseless/miniansi")

Future possibilities

  • Slice documents, copy one document into another
  • Image/raster support
  • Manually simulate alpha blending by tinting colours
  • Anti-aliasing
  • Figlet support

Simple example of creating an ANSI document

library(miniansi)

doc <- ANSI$new(width = 80, height = 20, background = 'cornflowerblue')
doc$rect(5, 5, 15, 15, colour = 'red', fill = 'white')
doc$line(1, 1, 80, 20, 'gold')
doc$circle(25, 10, r = 17, colour = 'darkgreen', fill = NA)
doc$text(50,  5, "Hello #RStats" , angle = 90, colour = 'brown')
doc$text(60, 20, "coolbutuseless", angle =  0)

doc

Aspect ratio tweaks

Working with ANSI is often painful due to the fundamental pixel unit not being square.

miniansi includes a mode which lets you specify the aspect ratio of the font, and tries to adjust everything else (by scaling the y values) so that square things still look square.

Use the font_aspect argument during initialisation to let miniansi know how to compensate for aspect ratio.

This mode has its own problems (as evidenced in the plot below). Note:

The Good:

  • The rect sides now actually look (nearly) the same length

The Bad:

  • Even though the document is only nominally 20 characters high, because of the y-coord adjustment, the visible plotting area is larger i.e. doc$max_height = height/font_aspect
  • Vertical text writing will never work properly in this mode.

The Weird:

  • Circles are always fudged by miniansi to look circular. This was a deliberate choice in order to work sanely with grid graphics and ggplot2::geom_point
library(miniansi)

doc <- ANSI$new(width = 80, height = 20, background = 'cornflowerblue', font_aspect = 0.45)

max_height <- doc$max_height

doc$rect(5, 5, 15, 15, colour = 'red', fill = 'white')
doc$line(1, 1, 80, max_height, 'gold')
doc$circle(25, 10, r = 17, colour = 'darkgreen', fill = NA)
doc$text(50,  5, "Hello #RStats" , angle = 90, colour = 'black')
doc$text(60, 20, "coolbutuseless", angle =  0)

doc

Alternate logo - Rendering a vector font to ANSI

library(dplyr)
library(hershey)  # Hershey Vector Fonts: https://github.com/coolbutuseless/hershey

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Initialise a document
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
doc <- ANSI$new(width = 110, height = 50, font_aspect = 0.4, background = 'white')

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Set some sizes
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
w  <- 110
h  <- doc$max_height
ll <- h/2.1

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Set up a hexagon
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
polygon_df <- tibble(
  angle = seq(0, 2*pi, length.out = 7) + pi/6,
  x     = ll * cos(angle) + w/2,
  y     = ll * sin(angle) + h/2
)

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Draw the polygon
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
doc$polygon(polygon_df$x, polygon_df$y, fill = 'cornflowerblue', colour = 'darkblue')

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Set up the letter R
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
letter_r <- hershey::hershey %>%
  filter(char == 'R', font == 'music') %>%
  mutate(
    x = 3.3 *  x,
    y = 3.3 * -y,
    
    x = x + w/2,
    y = y + h/2 + 2
  )


#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Draw the letter
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
letter_r %>% 
  group_by(stroke) %>% 
  group_walk(~doc$polyline(.x$x, .x$y, colour = 'darkred'))


#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# print
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
doc

Animation

This is a gif capture of this code running in my terminal. I definitely tweaked the frame rate up, as capturing a clean set of images in the terminal was hard.

library(miniansi)
library(dplyr)
library(hershey)  # Hershey Vector Fonts: https://github.com/coolbutuseless/hershey

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Set up the positions for each frame
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
xoffs  <- seq(135, -265, -10)
angles <- seq(pi/6, pi/2, length.out = length(xoffs))


#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Loop over all frames
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
for (idx in seq_along(xoffs)) {
  
  xoff  <- xoffs[idx]
  angle <- angles[idx]
  
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # Set up an ANSI document
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  doc <- ANSI$new(width = 110, height = 50, font_aspect = 0.4, background = 'white')

  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # Define some sizes
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  w  <- 110
  h  <- doc$max_height
  ll <- h/2.3
  
  
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # draw a rotated hexagon
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  polygon_df <- tibble(
    angle = seq(0, 2*pi, length.out = 7) + angle,
    x     = ll * cos(angle) + w/2,
    y     = ll * sin(angle) + h/2
  )
  
  doc$polygon(polygon_df$x, polygon_df$y, fill = 'cornflowerblue', colour = NA)
  
  
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # Create a word in the hershey 'meteorology' font
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  rstats <- hershey::create_string_df("RStats", font = 'meteorology') %>%
    mutate(
      x = 3 *  x,
      y = 3 * -y,
      y = y + h/2,
      x = x + xoff  # (-2*w-45), w+25 => -265, 135
    )
  
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # Draw the word using polylines for each stroke for each character
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  rstats %>% 
    group_by(char_idx, stroke) %>% 
    group_walk(~doc$polyline(.x$x, .x$y, colour = 'darkred'))
  
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  # Print the frame, reset the cursor to top of screen for a slightly 
  # cleaner render
  #~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
  print(doc)
  cat("\033[0;0H")
  Sys.sleep(0.2)
}