Control of letter spacing for text stimuli

This is quite a general post about the capabilities of presenting text stimuli in Psychopy, just to check that I’ve not missed anything.

I’d like to present a sentence (~60 characters) moving across the screen. It will be presented in the visual periphery, where crowding is prevalent, so I’d like to be able to independently control the letter spacing (increasing the letter spacing can alleviate crowding) and update positions on each frame. There are two options for displaying text in Psychopy, TextStim and TextBox, and as far as I can tell, neither allows control of inter-letter spacing. The solution that I have come to is that I’ll need to iterate through the sentence, setting the text object to be each single character from the sentence, and then update the text object position for each character. So, on each frame, I’ve got some pseudocode like this:

for charN in letters2disp:
>thisText.text = sentence[charN]
>thisText.pos = [nextPosition,0]
>thisText.draw()
win.flip()

[where > means indent]

I’m updating a single text object, rather than using a text object for each character, as this would become cumbersome (and memory hogging) very quickly. Also, the number of letters2disp may change, so a single object is much more flexible.

Given the drawing latencies for text stimuli (see the first table here: TextBox — PsychoPy v2021.1), I’m going to start running into problems quickly, trying to update and draw 60 characters per frame (~12 ms). Fortunately, in my case, I’m only displaying 5/6 characters per frame, through an aperture. So, I work out which characters will be visible and update their identity and position before flipping the win. However, even with 5/6 characters I feel like I might start to drop frames… and what if I’d like to increase the number of characters I’m displaying?

Some questions. Is there a way to very quickly update and draw text stimuli in Psychopy that I’m missing? Is there another solution to my problem that I’m missing? I’m looking at using TextBox, as TextStim is seemingly too slow to update >1 time during a frame, whereas I should be able to update TextBox 5/6 times during a frame (see ‘Change text + redraw time’ in the table referenced above). Is this problem with drawing latencies a general problem with displaying text or are there solutions readily available?

The spacing of letters in TextBox is defined by the character’s glyph in the font file, so unfortunately I think this is fixed in place. Depending on how small you need the intervals to be, you could do this by adding x spaces between each character, like this:

spaces = "" 
for n in range(numSpaces):
    spaces += " "
myTextbox.text = spaces.join(list(originalText))

Hi, that’s a great idea which I was initially using. It could possibly work but I’d need to know exactly how big the space was (in order to specify the distance for publication later). Also, I’ve a feeling that I’ll need more control over the spacing than that approach would afford me - it’s likely that I’ll vary the letter spacing in a staircase to test the optimal spacing. Still, it’s a good fallback if my current approach leads to too much frame-dropping.

I don’t think that having every letter as a separate TextStim object would be a problem so long as you only update the position when you actually want it to change.

I’ve tried out a few options with TextBox, it’s a bit of a code-heavy solution but I have something that will work!

The first thing you need to make sure of for this to work is that the textbox is loading its font before you make any changes to sizes, you can do this in the Begin Routine tab of a :code: Code component like so:

# Get a list of all latin characters, replace 02AF with the max value of whatever character block you need if there are characters not caught: https://en.wikipedia.org/wiki/Unicode_block
chars = [chr(n) for n in range(int("02AF", 16))]
# Pre-load these characters to the textbox's font object
textbox.glFont.fetch(chars)

Once you’ve done this, you need to set the advance of each glyph to be bigger than default, which you can do like this:

# Iterate through each glyph in the font
for key in textbox.glFont.glyphs:
    # Get the glyph's handle
    glyph = textbox.glFont.glyphs[key]
    # Multiply its advance distance by however much spacing you want
    glyph.advance = (glyph.advance[0]*mySpacingMultplier, glyph.advance[1])
    # Tell the textbox to work out its layout again
    textbox._layout()

When you run this it should make a textbox whose spacing is mySpacingMultiplier more than the font’s default (I recommend testing it with mySpacingMultiplier being something massive like 10 so you can easily see whether it’s having an effect)

1 Like

Hi Todd,

Thanks, this looks promising, I’ll have a play around with it.

Hi Todd, I’ve tried to run the code but I get this error:

AttributeError: module ‘psychopy.visual.textbox’ has no attribute ‘glFont’

…which is a bit weird.

I thought maybe it could be a version issue? I’m running version 2020.2.5 through Anaconda.

Cheers

It looks like you’re trying to get the font of the module textbox rather than your textbox - what did you import the module as, and what did you name your textbox?

TextBox has changed a lot since 2020.2.5, so it may be worth updating, but this might still work

I think I’ve sorted that now, but still getting the same error. I’ll update to see if that fixes it.

from psychopy.visual import textbox
from psychopy import visual, monitors

mon = monitors.Monitor('testMonitor')
winSize = [1280,800]
win = visual.Window(winSize, fullscr=True, winType='pyglet',color = -1,
        monitor=mon, units ='pix')

target=visual.TextBox(
    window=win,
    text="H",
    font_size=32,
    font_color=[1,1,1],
    pos=(0.0,0.0),
    size=(30,30),
    units='pix')

# Get a list of all latin characters, replace 02AF with the max value of whatever character block you need if there are characters not caught: https://en.wikipedia.org/wiki/Unicode_block
chars = [chr(n) for n in range(int("02AF", 16))]
# Pre-load these characters to the textbox's font object
target.glFont.fetch(chars)

I think what you want is TextBox2, not TextBox, as TextBox is on the road to deprecation