I wrote a function that draws wrapped text to a window and I thought I'd share it here, since I know other people will find it useful. If the developers want to add it to PsychToolbox, that would be great.
The function takes a width, specified in pixels, and it can handle \n characters. It works by adding one word at a time and testing whether the text exceeds the specified width, using TextBounds. I don't know if it's fast enough to do lots of text in a single screen refresh, but it should be fine for most static displays of text.
I've included the function and a bit of test code that shows how to use it. Hopefully it will display properly; in case it doesn't, the files are also attached.
-Winston
====================================================
==========================================================
The function takes a width, specified in pixels, and it can handle \n characters. It works by adding one word at a time and testing whether the text exceeds the specified width, using TextBounds. I don't know if it's fast enough to do lots of text in a single screen refresh, but it should be fine for most static displays of text.
I've included the function and a bit of test code that shows how to use it. Hopefully it will display properly; in case it doesn't, the files are also attached.
-Winston
====================================================
% Disable sync testing (needed because we're opening a small window on the screen)
Screen('Preference', 'SkipSyncTests', 1);
[win rect] = Screen('OpenWindow', 0, [0 0 0], [20 20 400 300]);
Screen('TextFont', win, 'Helvetica');
% Some text, with carriage returns
txt = [ 'This is a long line of text that will need to be wrapped many times. ' sprintf('\n\n') ...
'This is yet another line of text that will need to be wrapped many times.'];
drawTextWrapped(win, txt, 300, 50, 50, [255 255 255]);
% This would wrap it to the entire window instead
%drawTextWrapped(win, txt);
screen('Flip',win);
WaitSecs(.5);
==========================================================
function rect = drawTextWrapped(win, txt, width, startx, starty, color)
% rect = drawTextWrapped(win, txt, width, startx, starty, color)
%
% Draws wrapped text to a window.
% - win: Window pointer
% - txt: The text
% - width: Width to wrap to, in pixels. Defaults to width of the entire window
% - startx, starty: The starting coordinates. Defaults to (0,0)
% - color: Color of the text. Defaults to white (255 255 255).
if (nargin < 2) || isempty(txt)
txt = '';
end
% Default width is entire window
if (nargin < 3) || isempty(width)
rect = screen('Rect', win);
width = rect(3) - rect(1);
end
if (nargin < 4) || isempty(startx)
startx = 0;
end
if (nargin < 5) || isempty(starty)
starty = 0;
end
% Default to white text
if (nargin < 6) || isempty(color)
color = [255 255 255];
end
% Keep track of the y position after every line
yOffset = 0;
% Find the newline characters
newlineIdx = find(txt == sprintf('\n'));
% Pretend there's a newline at the beginning end (for later use)
newlineIdx = [0 newlineIdx length(txt)+1];
% Mark which newline we're on
nc = 1;
% Iterate over each block of text between newlines
while nc < length(newlineIdx)
% Get the current line (between \n markers, but not including them)
linetxt = txt( newlineIdx(nc)+1 : newlineIdx(nc+1)-1 );
% Make sure not to have a totally empty string (TextBounds won't like it)
if isempty(linetxt)
linetxt = ' ';
end
% Find all the spaces
delimIdx = find(linetxt == ' ');
% Pretend there's a delimiter at the beginning end (for later use)
delimIdx = [0 delimIdx length(linetxt)+1];
% The delimiter that marks the start of the current on-screen line
startdc = 1;
% Draw each piece of text (between \n markers) on the screen, wrapped
while startdc < length(delimIdx)
% End delimiter marker (start with 2 words)
enddc = startdc + 2;
% Make sure not to go past the end of the string
if enddc > length(delimIdx)
enddc = length(delimIdx);
end
% Keep adding words until it doesn't fit in the specified width
wrapTestFinished = 0;
while ~wrapTestFinished
startOffset = delimIdx(startdc) + 1;
% The ending offset is everything up to (but not including) the
% end-mark delimiter
endOffset = delimIdx(enddc) - 1;
% Test the boundaries
textBox = Screen('TextBounds', win, linetxt(startOffset:endOffset));
if textBox(3) > width
% If textBox is longer than the window width, end
wrapTestFinished = 1;
% Since the last value of dc represented a string that was too long,
% subtract one to get the string that fits.
enddc = enddc - 1;
elseif enddc == length(delimIdx)
% If we've hit the end of the string, end
wrapTestFinished = 1;
else
% Otherwise, add another word and keep going
enddc = enddc+1;
end
end
endOffset = delimIdx(enddc) - 1;
% Draw the text on the screen
Screen(win, 'DrawText', linetxt(startOffset:endOffset), ...
startx, starty+yOffset, [255 255 255]);
% Add the text's height to the offset counter
yOffset = yOffset + textBox(4);
% Mark the beginning of the next on-screen line
startdc = enddc;
end
% Move to the next newline character
nc = nc+1;
end
rect = [startx starty startx+width starty+yOffset];