Calculate the width of a string in a specific font and font size (without creating any files in batch mode)

Viewed 147

There is a similar question here that is asking for the width of a string in the default font size. However, I want to calculate the width of a text string in a specific font and font size.

The Base R strwidth() function seems to be ignoring changes to the font and family parameters. Observe:

  strwidth("This is cool.", units = "inches", font = 12, family = "sans")
  # [1] 0.9479167
  
  # font 10 gives same result as font 12 
  strwidth("This is cool.", units = "inches", font = 10, family = "sans")
  # [1] 0.9479167
  
  # monospace font gives same result as sans serif 
  strwidth("This is cool.", units = "inches", font = 10, family = "mono")
  # [1] 0.9479167

So it appears the font and family parameters are not picked up by the function.

I have found that setting the graphics parameters with par() makes the strwidth() function work correctly:

  par(ps = 12, family = "sans")
  strwidth("This is cool.", units = "inches")
  # [1] 0.8541667
  
  par(ps = 10, family = "sans")
  strwidth("This is cool.", units = "inches")
  # [1] 0.7291667
  
  par(ps = 10, family = "mono")
  strwidth("This is cool.", units = "inches")
  # [1] 1.083333

And these widths seem like decent estimates.

Now the issue is that par() is creating empty PDF files in my project, because no graphics device has been passed to it. My issue is, I'm not creating any graphics. I'm just trying to find the width of a string. I shouldn't have to set the graphics parameters to do this.

So the question still remains: How do I find the width of a string of a specific font and font size in R (without creating any files)?

2 Answers

After a few hours hacking away, I discovered one way to do it using the R.devices package:

get_text_width <- function(txt, font, font_size = 10, units = "inches") {
  
  f <- "mono"
  if (tolower(font) == "arial")
    f <- "sans"
  else if (tolower(font) == "times")
    f <- "serif"
  
  R.devices::devEval("nulldev", {
    par(family = f, ps = font_size)
    ret <- strwidth(txt, units = units) 
  })
  
  return(ret)
}

And here is the function in action:

  get_text_width("This is cool.", "Arial", 12)
  # [1] 0.8798333
  
  get_text_width("This is cool.", "Arial", 10)
  # [1] 0.7331944
  
  get_text_width("This is cool.", "Times", 10)
  # [1] 0.6829167

  get_text_width("This is cool.", "Courier", 10)
  # [1] 1.083333
  
  get_text_width(c("Hello", "Goodbye now!"), "Courier", 10)
  # [1] 0.4166667 1.0000000

I'd very much like to know if there are other ways to do it, as this seems like an unnecessary amount of work. Also I'd very much like to know if this works on other operating systems besides Windows.

For batch use, consider this test of the material in the pdf help page for the file parameter in grDevices-package which is loaded by default from the base packages installed as part of the core suite:

file
a character string giving the file path. If it is of the form "|cmd", the output is piped to the command given by cmd. If it is NULL, then no external file is created (effectively, no drawing occurs), but the device may still be queried (e.g., for size of text).

Running this file that I named "parwidth.R":

pdf(NULL) # and this could be opened with additional parameters
par(ps = 12, family = "sans")
strwidth("This is cool.", units = "inches")
dev.off()

... from a Terminal session with Rscript parwidth.R ...

does not produce a 'Rplots.pdf' file and does return the same value as above. I suspect that you may eventually need to look at the code of the first function called by pdf, namely getAnywhere(initPSandPDFfonts). I suspect it will vary from OS to OS.

Related