Unfortunately this doesn't work. Salesforce lists this as a known issue marked as no fix.
Long line of text doesn't wrap properly when PDF is generated via
visualforce. It also doesn't respect CSS word-wrap and word-break
properties and those do not work properly when set to break-work and
break-all, respectively.
Like @bobbuzzard suggests the only option is to manually break up long strings yourself, which not surprisingly is no trivial task.
Update
Salesforce has released a pilot version of their new improved pdf rendering engine (details here). Being based on a chromimum engine the output matches what debugging results in chrome and has full support for css paged media. While it does appear to have some scalability issues associated with the current version, it's stated intention is to add
broader support for modern HTML standards, such as CSS3, JavaScript,
and HTML5
One might reasonably hope issues like this are marked as non-fix as they're expected to be addressed by the new and improved rendering engine.
Update 2
At least for my use case (email attachments), the new pdf rendering isn't available yet. I wrote a word wrap helper component that may be useful to others, it's based on this answer.
LineSplitter.component
<apex:component access="global" layout="none" selfClosing="true" controller="LineSplitter">
<apex:attribute name="input" type="String" description="Input text (required)" assignTo="{!myInput}" />
<apex:attribute name="lineLength" type="Integer" description="Line length (defaults to 70)" assignTo="{!myLineLength}"/>
<apex:outputText value="{!output}" escape="false"/>
</apex:component>
LineSplitter.cls
/*
Developer: Ralph Callaway <[email protected]>
Description:
Utility for LineSplitter.component, splits input into multiple lines if needed.
Designed specifically for pdf rendering, not tested or intended for other
purposes (although you could probably get it there).
*/
public class LineSplitter {
public static final Integer DEFAULT_LINE_LENGTH = 70;
// pdf renderer doesn't convert new lines into breaks, so have
// to manually use <br/>
public static final String NEW_LINE = '<br/>';
// anything in here is where we try to split words if we can
private static String SPLIT_CHARS = ' -';
// assignTo variable names can't match attribute names, hence the 'my' prefix
public Integer myLineLength { get; set; }
public String myInput { get; set; }
public String output {
get {
if (output == null) {
output = splitLines(myInput, myLineLength);
}
return output;
}
private set;
}
// pre-split on existing line breaks before passing to line splitter
// since vf pdf rendering ignores line breaks
private static String splitLines(String input, Integer lineLength) {
if (input == null) return input;
List<String> finalLines = new List<String>();
for (String singleLine : input.split('\n')) {
finalLines.add(splitLinesSingleLine(singleLine, lineLength));
}
return String.join(finalLines, NEW_LINE);
}
// splits single lines, core splitting
// special thanks to @icr for providing something that was a huge
// stretch to get working in Apex https://stackoverflow.com/a/17635/84130
private static String splitLinesSingleLine(String input, Integer lineLength) {
if (lineLength == null) { lineLength = DEFAULT_LINE_LENGTH; }
if (input == null || input.length() <= lineLength) return input;
String[] words = explode(input, SPLIT_CHARS);
Integer curLineLength = 0;
List<String> strBuilder = new List<String>();
for(Integer i = 0; i < words.size(); i += 1)
{
String word = words[i];
// If adding the new word to the current line would be too long,
// then put it on a new line (and split it up if it's too long).
if (curLineLength + word.length() > lineLength) {
// Only move down to a new line if we have text on the current line.
// Avoids situation where wrapped whitespace causes emptylines in text.
if (curLineLength > 0) {
strBuilder.add(NEW_LINE);
curLineLength = 0;
}
// If the current word is too long to fit on a line even on it's own then
// split the word up.
while (word.length() > lineLength) {
strBuilder.add(word.subString(0, lineLength - 1) + '-');
word = word.subString(lineLength - 1);
strBuilder.add(NEW_LINE);
}
// Remove leading whitespace from the word so the new line starts flush to the left.
// ??? no trimStart() analogue in apex, trying with trim(), revisit if there are
// issues
// word = word.TrimStart();
word = word.trim();
}
strBuilder.add(word);
curLineLength += word.length();
}
String combinedLine = String.join(strBuilder, '');
// clear out any trailing whitespace on lines
combinedLine = combinedLine.replaceAll('\\s*<br/>', '<br/>');
return combinedLine;
}
// tokenize thing into word parts based on split characters
private static String[] explode(String str, String splitCharacters) {
List<String> parts = new List<String>();
if (str != null) {
Integer startIndex = 0;
while (true) {
Integer preIndex = str.subString(startIndex).indexOfAny(splitCharacters);
Integer index = startIndex + preIndex;
if (preIndex == -1) {
parts.add(str.subString(startIndex));
break;
}
String word = str.subString(startIndex, index);
String nextChar = str.subString(index, index + 1);
// Dashes and the likes should stick to the word occuring before it. Whitespace doesn't have to.
if (nextChar.isWhiteSpace()) {
parts.Add(word);
parts.Add(nextChar);
} else {
parts.Add(word + nextChar);
}
startIndex = index + 1;
}
}
return parts;
}
}
LineSplitter_Test.cls
/*
Developer: Ralph Callaway <[email protected]>
Description:
Test methods for LineSplitter.cls
*/
@isTest
private class LineSplitter_Test {
@isTest
private static void nullTest() {
runTest(null, null, null);
}
@isTest
private static void noSplitTest() {
runTest('asdf', null, 'asdf');
}
@isTest
private static void simpleSplit() {
String input = 'A sample string to split.';
Integer lineLength = 13; // split is middle of 'string'
String output = 'A sample' + LineSplitter.NEW_LINE +
'string to' + LineSplitter.NEW_LINE +
'split.';
runTest(input, lineLength, output);
}
@isTest
private static void codeSplit() {
String input = 'BEADI-MTVAD-9SEAA';
Integer lineLength = 7;
String output = 'BEADI-' + LineSplitter.NEW_LINE +
'MTVAD-' + LineSplitter.NEW_LINE +
'9SEAA';
runTest(input, lineLength, output);
}
@isTest
private static void longWord() {
String input = 'veryverylongword';
Integer lineLength = 10;
String output = 'veryveryl-' + LineSplitter.NEW_LINE +
'ongword';
runTest(input, lineLength, output);
}
private static void runTest(String input, Integer lineLength, String expected) {
LineSplitter splitter = new LineSplitter();
splitter.myInput = input;
splitter.myLineLength = lineLength;
System.assertEquals(expected, splitter.output);
}
}