17

I cannot get word-wrap to work in a VisualForce page, which is rendered as a PDF. There is a table with a column called Comments and this contains a long string without any spaces. The long string is pushing the rest table off the PDF page.

I attempted to use the CSS attribute word-wrap: break-word; but this is not working. This is because the word-wrap attribute was introduced in CSS 3 and SalesForce uses Flying Saucer to render there PDF.

Flying Saucer only supports a handful of CSS 3 attributes and the word-wrap attribute is not one of them. Flying Saucer has strong support for CSS 2.1.

Is there a way to wrap words in CSS 2.1/Flying Saucer?

Here is my VisualForce page:

<apex:page renderAs="pdf" Controller="TaskController">
<head>
.wrapWithWordBreak {
    word-wrap: break-word;
}
</head>
    <table class="bordered">
        <thead>
            <tr>
                <th>Subject</th>
                <th>Comments</th>
                <th>Date</th>
                <th>Address</th>
            </tr>
        </thead>
        <tbody>
            <apex:repeat value="{!tasks}" var="t">
            <tr>
                <td><apex:outputText value="{!t.Subject}"/></td>
                <td class="wrapWithWordBreak"><apex:outputText value="{!t.Comments}"/></td>
                <td><apex:outputField value="{!t.Date}"/></td>
                <td><apex:outputField value="{!t.Address}"/></td>
            </tr>
            </apex:repeat>
        </tbody>
    </table>
</apex:page>

Here is my controller:

public class TaskController
{
    public list<Task> tasks{get;set;}

    public TaskController()
    {
        tasks =[SELECT Subject, Comments, Date, Address from Task];
    }
}

Table that is displayed in rendered pdf:

enter image description here

8
+50

In the Code Coverage podcast with Josh Birk there was mention of an improved PDF rendering engine pilot at the 19 minute 30 second mark.

I couldn't find mention of it on Google or in the IdeaExchange Pilot/Beta list.

It sounds like you need to raise a ticket or contact Josh directly to get in the pilot.

Will it solve you word-wrap issue? I don't know. But getting on the pilot would be a good way to get it supported in the future.

Update Summer '17 - Use Advanced PDF to Render Visualforce Pages as PDF Files (Pilot)

  • 1
    If you are a partner you can get in on the pilot from p.force.com/pilots (requires community log in). – Peter Knolle Oct 21 '14 at 2:48
  • @PeterKnolle unfortunately this one isn't listed there, even though it's been added to the summer '17 release notes – Ralph Callaway May 29 '17 at 16:45
4

No, this is not possible. Even the CSS 2.1 concepts that should work, for example:

white-space: pre;           /* CSS 2.0 */
white-space: pre-wrap;      /* CSS 2.1 */

are ignored. It seems like every tool that uses flying saucer has outstanding bugs around this:

https://www.google.co.uk/search?q=flying+saucer+word+wrap

I handle this server side using a wrapper class to encapsulate the task and a processed version of a text field that has hyphens or spaces inserted to break up long words. Its still a little flaky, especially when the long words appear in the middle of the text, and it does mean that the controller knows a little too much about the view, but its that or the long words spill out over the next table cell, so I put up with it.

  • is flying saucer what's being used for pdf generation currently? – Ralph Callaway May 29 '17 at 16:31
  • 2
    Yes. Summer 17 has a new PDF renderer in pilot so hopefully we'll get an improved experience in a release or two. – Bob Buzzard May 31 '17 at 11:07
  • bollocks, got on the pilot, but happened to need it for email template attachments, which isn't supported yet, hopefully soon. i ported a word wrap algorithm to apex in the meantime (see answer below) – Ralph Callaway Jun 2 '17 at 15:04
2

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 <ralph@callaway.cloud>
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 <ralph@callaway.cloud>
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);
    }
}
  • 1
    I think Advanced PDF generation which pilot in summer 17 may help. – gold May 25 '17 at 20:27
  • 1
    Unfortunately for me the renderAs="advanced_pdf" does not solve the issue. Need to manually split the lines like Ralph has done. – Doug Ayers Jan 3 '18 at 1:44
0

I had a same problem with URL in pdf but it was a long text field. The word wrap was not working for the URL as there are no white spaces in it. The URL was not wrapping and it was going beyond the page. i used the below code to add white spaces to over come this issue.

public static string insertBreak(String hyperLink) { // hyperLink is the URL text
        final Integer linkLength = hyperLink.length();
        final Integer maxLength = 100;
        Integer varCount = (linkLength/maxLength);

        if (math.mod(linkLength, maxLength) > 0)
            varCount++;

        String fullLink = '';
        integer startValue = 0;
        integer endValue = maxLength;
        for (Integer i = varCount; i >= 0; i--) { 
            fullLink += hyperLink.substring(startValue, endValue) + ' ';
            startValue = endValue;
            endValue = (endValue + maxLength) > linkLength ? linkLength : (endValue + maxLength);
        }
        return fullLink;
    }

While rendering the page i will display the URL text with white spaces only when the page is rendered as PDF. otherwise i will display the normal URL text without white spaces.

Hope this helps.... :)

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.