Drawing wrapped text

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

====================================================

% 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];