Fork us on GitHub

TIP: Lightweight Rich Text View

Use existing layout and functionality to provide rich input UI
TIP: Lightweight Rich Text View

TIP: Lightweight Rich Text View

A very common request is support for rich text in Codename One, this is hard to do in a generic/performant cross platform way but can be done as I explained in my answer here. This is pretty common though so it might be worth it doing this in a generic way.

Building a "proper" rich text view would take some work as there are many edge cases & complexities. However, since we already have an XML parser that’s perfectly capable of parsing HTML I decided to try a very simple HTML based syntax just to show how easy it would be to create something generic like this. I avoided the "link" use case as that would require some link handler code and I avoided font sizes/colors as I didn’t want to go into attributes and related complexities.

Instead I just added support for bold and italic while demonstrating that simple things like line breaks still work:

Form hi = new Form("Rich Text", BoxLayout.y());

class RichTextView extends Container {
    private String text;
    public RichTextView() { (1)
    }

    public RichTextView(String text) {
        setText(text);
    }

    public final void setText(String text) {
        this.text = text;
        final Font defaultFont = Font.createTrueTypeFont("native:MainRegular", "native:MainRegular"); (2)
        final Font boldFont = Font.createTrueTypeFont("native:MainBold", "native:MainBold");
        final Font italicFont = Font.createTrueTypeFont("native:ItalicRegular", "native:ItalicRegular");
        final int sizeOfSpace = defaultFont.charWidth(' '); (3)
        XMLParser parser = new XMLParser() {
            private Font currentFont = defaultFont;
            @Override
            protected void textElement(String text) {
                if(text.length() > 0) {
                    if(text.indexOf(' ') > -1) {
                        for(String s : StringUtil.tokenize(text, ' ')) {
                            createComponent(s);
                        }
                    } else {
                        createComponent(text);
                    }
                }
            }

            private void createComponent(String t) {
                Label l = new Label(t);
                Style s = l.getAllStyles();
                s.setFont(currentFont); (4)
                s.setPaddingUnit(Style.UNIT_TYPE_PIXELS);
                s.setPadding(0, 0, 0, sizeOfSpace);
                s.setMargin(0, 0, 0, 0);
                add(l);
            }

            @Override
            protected boolean startTag(String tag) {
                switch(tag.toLowerCase()) {
                    case "b":
                        currentFont = boldFont;
                        break;
                    case "i":
                        currentFont = italicFont;
                        break;
                }
                return true;
            }

            @Override
            protected void endTag(String tag) {
                currentFont = defaultFont;
            }

            @Override
            protected void attribute(String tag, String attributeName, String value) {
            }

            @Override
            protected void notifyError(int errorId, String tag, String attribute, String value, String description) {
                Log.p("Error during parsing: " + tag);
            }

        };
        try {
            parser.eventParser(new CharArrayReader(("<body>" + text + "</body>").toCharArray())); (5)
        } catch(IOException err) {
            Log.e(err);
        }
    }

    public String getText() {
        return text;
    }
}

hi.add(new RichTextView("This is plain text <b>this is bold</b> and <i>this is italic</i> and all of this breaks lines nicely as well...."));

hi.show();

This code produces the image below. Notice the following things about it:

1 The default layout is FlowLayout which works well for simple things like that but might be a little flaky for complex use cases
2 For simplicity I just hardcoded the fonts
3 I removed the spaces and padding/margin. Then I used the width of the spaces to re-add a space in the form of padding. This allows line breaks on word boundaries
4 Here I reset the individual padding/margin to 0 except for the space (see <3>)
5 I need to wrap the text in a <body> tag as XML requires one parent tag. I use the event callback parser instead of DOM as it is a bit more convenient (and faster)
The demo above running in the simulator
Figure 1. The demo above running in the simulator

Moving On

The obvious question is "why isn’t this in Codename One?".

This is a proof of concept, the devil is in the details with things such as this and once we start going into them this will drag us down a huge rabbit hole. However, your personal use case might not be as extensive as ours would need to be. With this as a starting point I’m sure most use cases could be adapted to handle some form of rich text within your app.

I chose to use HTML because I already had a parser but the basic concept should work well for any markup language out there.

Share this Post:

Posted by Shai Almog

Shai is the co-founder of Codename One. He's been a professional programmer for over 25 years. During that time he has worked with dozens of companies including Sun Microsystems.
For more follow Shai on Twitter & github.