Well, it’s not perfect but after a lot of trial-and-error I have something that works much better! Also will add hyphens if you ask for them (and you can specify the hyphen character used) as well as the ability to add indentation to lines after the first one (one of the reasons I started this whole crazy process).
(Mostly) supports LEFT
, CENTER
, and RIGHT
alignment too.
Update: got this working a bit better with hyphenation and various alignments.
String s = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
//String s = "The quick brown fox jumps over the lazy dog";
//String s = "Thequickbrownfoxjumps overthelazydoglazydog";
int posX = 50;
int posY = 50;
int textWidth = 400;
PFont font;
void setup() {
size(500,800);
surface.setLocation(0,0);
background(255);
font = loadFont("CooperHewitt-Heavy-48.vlw");
textAlign(LEFT, TOP);
textFont(font);
textSize(48);
textLeading(48 * 1.3);
// wrapText() command will draw text auto-wrapped to a specified
// width (can also indent lines after the first and can add
// hyphens to very long words, if specified)
// returns the overall height of the text block created
fill(0);
noStroke();
float h = wrapText(s, posX,posY, textWidth);
// show overall height of rendered text
noFill();
stroke(255,150,0, 100);
rect(posX,posY, textWidth,h);
}
// wraps text to a specified width (will flow to any height necessary to fit
// all the text), can optionally indent first/other lines and will hyphenate
// very large words (can also specify a particular hyphen character if desired)
// via: https://stackoverflow.com/a/45614206/1167783
float wrapText(String s, float x, float y, float w, int _indentFirst, int _indentOthers, boolean addHyphen, String hyphenChar) {
// optional: specify the threshold for long words to be hyphenated
// try playing with this if you need to tune your hyphenation
// here four characters past the boundary = add a hyphen please
float hyphenBufferWidth = textWidth(" ") * 4;
// create indent strings from specified number of characters
// via: https://stackoverflow.com/a/2807731/1167783
String indentFirst = new String(new char[_indentFirst]).replace('\0', ' ');
String indentOthers = new String(new char[_indentOthers]).replace('\0', ' ');
// if short enough, just send it back as is
StringBuilder outputLines = new StringBuilder();
if (textWidth(s) <= w) {
outputLines.append(s);
}
// otherwise, split it!
else {
String[] words = s.split(" ");
StringBuilder currentLine = new StringBuilder();
currentLine.append(indentFirst);
for (int i=0; i<words.length; i++) {
String word = words[i];
// check width, if not too long yet then add the current word
// and keep going through the string
float lineWidth = textWidth(currentLine.toString() + " " + word);
if (lineWidth < w) {
currentLine.append(word + " ");
}
// if too long, end current line and start a new one
else {
// if this line is waaayy too long (probably one long word
// or a url, etc) then force a break
if (textWidth(currentLine.toString()) > w + hyphenBufferWidth) {
String[] parts = hyphenate(currentLine.toString(), w, addHyphen, hyphenChar);
outputLines.append(parts[0] + "\n"); // add current line to output
currentLine = new StringBuilder(); // start new line of text
if (g.textAlign == LEFT) currentLine.append(indentOthers); // add indent if specified
currentLine.append(parts[1] + " " + word + " "); // and add remaining words to new line
}
// otherwise, add this line to the output and start
// a new one with the current word
else {
outputLines.append(currentLine.toString() + "\n");
currentLine = new StringBuilder();
if (g.textAlign == LEFT) currentLine.append(indentOthers);
currentLine.append(word + " ");
}
}
}
// when out of words, add the current line
// to the output, adding one last line-break if this
// is a really long word like above
if (currentLine.length() > 0) {
if (textWidth(currentLine.toString()) > w + hyphenBufferWidth) {
String[] parts = hyphenate(currentLine.toString(), w, addHyphen, hyphenChar);
while (true) {
outputLines.append(parts[0].trim() + "\n");
if (textWidth(parts[1]) > w + hyphenBufferWidth) {
parts = hyphenate(parts[1], w, addHyphen, hyphenChar);
}
else {
outputLines.append(parts[1]);
break;
}
}
}
else {
outputLines.append(currentLine.toString());
}
}
}
// trim any unwanted newline chars
String out = outputLines.toString().replaceAll("^\n+", "");
//println(out.replace("\n", "\\n"));
// use the usual text() command to draw the string!
if (g.textAlign == LEFT) {
text(out, x, y);
} else if (g.textAlign == CENTER) {
text(out, x+w/2, y);
} else if (g.textAlign == RIGHT) {
text(out, x+w, y);
}
// count linebreaks to determine the overall text box height
int numLinebreaks = countLinebreaks(out);
return g.textSize + (numLinebreaks * g.textLeading) - g.textDescent();
}
float wrapText(String s, float x, float y, float w) {
return wrapText(s, x, y, w, 0, 0, false, "-");
}
float wrapText(String s, float x, float y, float w, int indentFirst) {
return wrapText(s, x, y, w, indentFirst, 0, false, "-");
}
float wrapText(String s, float x, float y, float w, int indentFirst, int indentOthers) {
return wrapText(s, x, y, w, indentFirst, indentOthers, false, "-");
}
float wrapText(String s, float x, float y, float w, int indentFirst, int indentOthers, boolean addHyphen) {
return wrapText(s, x, y, w, indentFirst, indentOthers, addHyphen, "-");
}
// returns the number of linebreaks in a string
int countLinebreaks(String s) {
return s.length() - s.replace("\n", "").length();
}
// splits long strings at a specified width, add hyphens
// if specified (they're left off by default)
String[] hyphenate(String currentLine, float w, boolean addHyphen, String hyphenChar) {
String firstHalf = currentLine;
String secondHalf = "";
for (int i=currentLine.length()-2; i>=0; i-=1) {
firstHalf = currentLine.substring(0, i);
secondHalf = currentLine.substring(i, currentLine.length()-1);
if (textWidth(firstHalf) <= w) {
// if hyphenating, move the last char from the first line to the start
// of the second line before adding the hyphen character
if (addHyphen) {
secondHalf = firstHalf.charAt(firstHalf.length()-1) + secondHalf;
firstHalf = firstHalf.substring(0, firstHalf.length()-1) + hyphenChar;
}
break;
}
}
return new String[] { firstHalf, secondHalf };
}