Search This Blog

Saturday 12 April 2014

Programmatically updating Rich Text (or xHTML)

In a previous sample I gave an example of updating the rich text of a draw object using E4X.  In this sample I want to show an alternative using the Span objects from the Adobe Reader API. 

The Span object does not cater for all the attributes of rich text that are supported in an XFA form but sometimes can be easier than using E4X.

So an example, the rich text "Please click the red button" is generated by the xHTML;

<body xmlns="http://www.w3.org/1999/xhtml" xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
    <p style="letter-spacing:0in">
        Please click the<span style="color:#ff0000">red</span>button
    </p>
</body>

As an array of Spans objects this is represented as

({text:"Please click the "})
({text:"red", textColor:["RGB", 1, 0, 0]})
({text:" button"})


Then the code to change all the red text to green would be;

var spans = util.xmlToSpans(Text1.value.exData["##xHTML"].saveXML());
for (var i = 0; i < spans.length; i++)
{

    if (spans[i].textColor && color.equal(spans[i].textColor, color.red))
    {

        spans[i].textColor = color.green;
        spans[i].text = "green";
    }
}

var xHTML = util.spansToXML(spans);
Text1.value.exData.loadXML(xHTML, falsetrue);

The color object used in this code is also from the Adobe Reader API, it defines some common colors  (that is black, white, dkGray, gray, ltGray, red, green, blue, cyan, magenta and yellow).  As well as the color.equal() method it also has a color.convert() method so to change all the colors to a gray scale we could;

var spans = util.xmlToSpans(Text1.value.exData["##xHTML"].saveXML());
for (var i = 0; i < spans.length; i++)
{

    if (spans[i].textColor)
    {
        spans[i].textColor = color.convert(spans[i].textColor, "G");
    }

}
var xHTML = util.spansToXML(spans);
Text1.value.exData.loadXML(xHTML, falsetrue);

If anyone still needs to have a color form for the screen and a black and white version for printing and uses rich text then this code could go in the prePrint event and then in the postPrint event you could revert to the original formatting with;

Text1.nodes.remove(Text1.value);
Removing the changes made in the Form DOM resets the properties to their initial values as defined in the Template DOM.

To build a rich text value from scratch we could;

var spans = [];

spans.push({text:"Hello "});

spans.push({text:NameField.rawValue, fontWeight:700, fontStyle:"italic"});
spans.push({text:", I hope you like this sample"});

var xHTML = util.spansToXML(spans);

Text1.value.exData.loadXML(xHTML, false, true);

If you need to change a plain-text Text field into a rich-text one then you can use the following code;

if (Text1.value.oneOfChild.className === "text")
{
    Text1.value.nodes.remove(Text1.value.text);
    var exDataNode = xfa.form.createNode("exData");
    Text1.value.nodes.append(exDataNode);
}

The sample SpanDemos.pdf includes a couple of other simple examples, as well as a more complicated example that will take the body element of the rich text then format and colorize it.  This sample also shows a workaround for the lack of support for the xfa-spacerun:yes style attribute in the Span objects.


background-color style attribute

This attribute is not mentioned in the XFA spec and there is no support for it with the Designer UI but Reader will render the background color, you just need to enter it in the XML Source view.  The trick with using background-color is that all elements following the one the style is applied to will inherit the color.  In an HTML page only the descendent elements would inherit the color.  This just means you need a second background-color style attribute to reset the color.  Designer doesn't play well with this attribute as every time you edit the text using the Design view you will have to add the second background-color style attribute back.

The color can be specified in hexadecimal or decimal;

background-color:#C4C4C4     or     background-color:rgb(196,196,196)

You can see an example of the background-color style attribute in this sample. 

I haven't gone though all the style attributes to see which ones are supported, but I did try adding a border and that didn't work for me.

14 comments:

  1. Hi,

    Thanks for this great post ! I've been trying to play with rich text in LiveCycle for a while and you pointed out some really useful informations.

    I have one question. Do you know how I could convert a rich text caption into a plain text caption ?
    I need to export the caption of a field into another text field but it doesn't work if I have italic and normal text into the same caption field.

    Thanks alot !

    ReplyDelete
  2. Hi Jonathan,

    I'm glad you found the post a help, extracting the plain text from a caption should be as easy as;

    console.println(TextField1.caption.value.exData["##xHTML"].value);

    You could also loop though each element,

    var spans = util.xmlToSpans(TextField1.caption.value.exData["##xHTML"].saveXML());
    var plainText = [];
    for (var i = 0; i < spans.length; i++)
    {
    if (spans[i].text)
    {
    plainText.push(spans[i].text)
    }
    }
    console.println(plainText.join(""));

    Good luck

    Bruce


    ReplyDelete
  3. Hi Bruce,

    I have a much simpler request (though this was helpful as context): I'm just trying to restrict the rich text formatting in a text field to bold and italic. Is this something I can do in xHTML?

    ReplyDelete
  4. Hi, You should be able to do a similar thing as Jonathon in the exit event of the field so when they leave the field any unwanted formatting is removed. That is;

    var spans = util.xmlToSpans(TextField1.value.exData["##xHTML"].saveXML());
    var boldItalicSpans = [];
    for (var i = 0; i < spans.length; i++)
    {
    var boldItalicSpan = {};
    if (spans[i].text)
    {
    boldItalicSpan.text = spans[i].text;
    }
    if (spans[i].endParagraph)
    {
    boldItalicSpan.endParagraph = spans[i].endParagraph;
    }
    for (var p in spans[i])
    {
    if ((p == "fontStyle" && spans[i][p] == "italic") || (p == "fontWeight" && spans[i][p] == 700))
    {
    boldItalicSpan[p] = spans[i][p];
    }
    }
    boldItalicSpans.push(boldItalicSpan);
    }

    var xHTML = util.spansToXML(boldItalicSpans);
    TextField1.value.exData.loadXML(xHTML, false, true);

    Here's a link to a form to make sure the code actually works, https://sites.google.com/site/livecycledesignercookbooks/home/BoldItalic.pdf?attredirects=0&d=1.

    If you want to remove line feeds then remove the "endParagraph" code.



    ReplyDelete
    Replies
    1. Thanks, Bruce! Works as advertised. The only small glitch I'm having is that it's also removing line and paragraph breaks. Any idea on what's going wrong?

      Delete
  5. Hi,

    The xmlToSpans method doesn't handle all the formatting available so you may always have some problems. One thing that it doesn't handle is the style="xfa-spacerun:yes", without this then consecutive white space (including line breaks) will get lost.

    You could try changing the code

    if (spans[i].endParagraph)
    {
    boldItalicSpan.endParagraph = spans[i].endParagraph;
    }

    to

    if (spans[i].endParagraph)
    {
    boldItalicSpan.endParagraph = spans[i].endParagraph;
    if (spans[i].text == " ")
    {
    boldItalicSpan.text = "\u2028";
    boldItalicSpan.endParagraph = false;
    }
    }

    Which might get you closer to what you want.

    So the xHTML <p><span style="xfa-spacerun:yes"> </span></p>, which represents a paragraph mark in the original rich text, just comes though as a space but with the endParagraph set to true. The change in code above replaces this with an actual line break character ... the sort that doesn't get collapsed in XML.

    And then this will only work with Reader 9 and later.

    There are some examples of using E4X in this blog to handle rich text, which is harder but does get you more control.

    ReplyDelete
    Replies
    1. Thanks again, Bruce. Works perfectly.

      One more silly question: if I want to keep underlines as well, do I just add p == "textDecoration" && spans[i][p] == "underline" after the pipes?

      Delete
    2. Underlining is a bit different, because there is double underline, word underline and double word underline. If you wanted to clean those up to just an underline you would want the following after

      if ((p == "fontStyle" ...)
      {
      ...
      }

      to add

      if (p == "underline" && spans[i][p])
      {
      boldItalicSpan[p] = "underline";
      }

      If you want to keep the type of underlines then it would be just (p == "underline") after the pipes.

      Delete
  6. How can you reset the background color to none or transparent after to set it? This is because the background color is applied to the end of the field after it is set. I tried everything I thing of nothing worked. I tried using white color but it looks funny when the field is active. Can you help please?

    ReplyDelete
    Replies
    1. I was not able to get this to work either, you can nest the span elements with the background-color style, so in this example only the word RED has a background color of Red

      <?xml version="1.0" encoding="UTF-8"?>
      <body xmlns="http://www.w3.org/1999/xhtml" xmlns:xfa="http://www.xfa.org/schema/xfa-data/1.0/">
      <p>
      <span style="background-color:#ffffff">
      Please click the
      <span style="background-color:#ff0000">
      RED
      </span>
      Button
      </span>
      </p>
      </body>

      Probably doesn't help but maybe easier if you have to update when the field is disabled

      Delete
    2. Actually, I tried everything. The only way was to insert a dummy span element with white background after change the background color. It looks funny only when the field is in interactive data-entry mode. When in read-only mode or in static PDF, then it is fine.

      Delete
  7. Thanks a lot for this great article. I am unable to convert plain text field to rich text field using script sample provided. I get error "Invalid append operation: value cannot have a child element of exData. The element [exData] has violated its allowable number of occurrences.". Also, when I use background-color then try to reset it to white, it will look funny. Can I disable the color or use transparent background color?

    ReplyDelete
    Replies
    1. Hi Tarek,

      You might need some code like the following to convert a 'normal' text field to a rich text one;
      if (TextField1.value.oneOfChild.className === "text") {

      TextField1.value.nodes.remove(TextField1.value.text);

      var exDataNode = xfa.form.createNode("exData");

      TextField1.value.nodes.append(exDataNode);

      }

      Delete
    2. Actually, it worked! That was awesome! I had to execute the lines of code that will do the conversion in one shut, not one by one. That was a great help.

      Delete