2

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)?

IRTFM
  • 258,963
  • 21
  • 364
  • 487
David J. Bosak
  • 1,386
  • 12
  • 22
  • @akrun Curious. And scary. I was assuming that the `par()` method worked, even though it created the PDF. But you are telling me that it doesn't even work on Mac. This is worse than I thought! – David J. Bosak Sep 14 '21 at 01:04
  • BTW, I'm on Windows. – David J. Bosak Sep 14 '21 at 01:09
  • But I need this to work correctly and uniformly across all operating systems, as it will be part of a package submitted to CRAN. – David J. Bosak Sep 14 '21 at 01:18
  • `strwidth` is documented to retrun a value for the current graphics device. I doubt very much that it is `par` that is creating empty pdf files. Rather I strongly suspect that you are opening a `pdf`-device and then failing to close it. You need to issue `dev.off()` after opening a `pdf` device if you don't want the empty files. I am so sure of this that I am voting to close as a typo/not reproducible equivalent. – IRTFM Sep 14 '21 at 01:53
  • @IRTFM Not only is it reproducible, it is documented in the device options: "The value of this option defaults to the normal screen device (e.g., X11, windows or quartz) for an interactive session, and **pdf** in batch use or if a screen is not available. " – David J. Bosak Sep 14 '21 at 02:33
  • @IRTFM In other words, you can reproduce it by running the par() example code I have above in batch. It creates a file called 'Rplots.pdf'. Other people have had this problem on SO: https://stackoverflow.com/questions/6535927/how-do-i-prevent-rplots-pdf-from-being-generated. Some people questioned that post also, but I've seen it myself in isolated code. If there is no device context, and you are running in batch, it generates a PDF file. – David J. Bosak Sep 14 '21 at 02:53
  • Yes, in batch, a condition which was not mentioned previously. So if the null device is the current device, then `pdf` is executed and a file is created (as documented in `?par`) "If the current device is the null device, par will open a new device before querying/setting parameters. (What device is controlled by options("device").)" – IRTFM Sep 14 '21 at 03:38
  • I removed my close vote. I could delete my comment that asserted non-reproducibility if you want to clean up the comment tree. – IRTFM Sep 14 '21 at 15:49

2 Answers2

1

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.

David J. Bosak
  • 1,386
  • 12
  • 22
  • 1
    I am able to replicate the same output in mac os – akrun Sep 14 '21 at 03:12
  • On my machine that code threw an error because the "R.devices" package was not installed. Hardly likely to succeed for the average user. (I've got almost 1500 packages in my library.) – IRTFM Sep 14 '21 at 03:37
1

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.

IRTFM
  • 258,963
  • 21
  • 364
  • 487
  • If you call this repeatedly, I'm getting an error about opening too many devices contexts. It appears that even though the pdf device is NULL, you still have to close it with `dev.off()`. – David J. Bosak Sep 14 '21 at 10:49