web-dev-qa-db-fra.com

Android: Comment utiliser le Html.TagHandler?

J'essaie de créer une application Android pour un babillard électronique. Pour afficher du HTML formaté pour le contenu du message, j'ai choisi TextView et la méthode Html.fromHtml (). Cela, malheureusement, ne couvre que quelques balises html. Les balises inconnues sont gérées par une classe qui implémente TagHandler et doit être générée par moi-même.

Maintenant, j'ai beaucoup cherché sur Google et je ne trouve pas d'exemple sur le fonctionnement de cette classe. Considérons que j'ai une balise u pour souligner du texte (je sais que c'est obsolète, mais peu importe). À quoi ressemble mon TagHandler?

Il est appelé de la manière suivante:

public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {

Les deux premiers arguments sont très bien. Je suppose que je dois modifier la sortie en utilisant output.append (). Mais comment puis-je y attacher quelque chose souligné?

44
janoliver

Donc, je l'ai finalement compris par moi-même.

public class MyHtmlTagHandler implements TagHandler {

    public void handleTag(boolean opening, String tag, Editable output,
            XMLReader xmlReader) {
        if(tag.equalsIgnoreCase("strike") || tag.equals("s")) {
            processStrike(opening, output);
        }
    }

    private void processStrike(boolean opening, Editable output) {
        int len = output.length();
        if(opening) {
            output.setSpan(new StrikethroughSpan(), len, len, Spannable.SPAN_MARK_MARK);
        } else {
            Object obj = getLast(output, StrikethroughSpan.class);
            int where = output.getSpanStart(obj);

            output.removeSpan(obj);

            if (where != len) {
                output.setSpan(new StrikethroughSpan(), where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
        }
    }

    private Object getLast(Editable text, Class kind) {
        Object[] objs = text.getSpans(0, text.length(), kind);

        if (objs.length == 0) {
            return null;
        } else {
            for(int i = objs.length;i>0;i--) {
                if(text.getSpanFlags(objs[i-1]) == Spannable.SPAN_MARK_MARK) {
                    return objs[i-1];
                }
            }
            return null;
        }
    }


}

Et pour votre TextView, vous pouvez appeler cela comme:

myTextView.setText (Html.fromHtml(text.toString(), null, new MyHtmlTagHandler()));

si quelqu'un en a besoin.

À votre santé

102
janoliver

Cette solution se trouve dans le Android sdk

Dans Android.text.html. Lignes 596 - 626. Copier/coller

private static <T> Object getLast(Spanned text, Class<T> kind) {
    /*
     * This knows that the last returned object from getSpans()
     * will be the most recently added.
     */
    Object[] objs = text.getSpans(0, text.length(), kind);

    if (objs.length == 0) {
        return null;
    } else {
        return objs[objs.length - 1];
    }
}

private static void start(SpannableStringBuilder text, Object mark) {
    int len = text.length();
    text.setSpan(mark, len, len, Spannable.SPAN_MARK_MARK);
}

private static <T> void end(SpannableStringBuilder text, Class<T> kind,
                        Object repl) {
    int len = text.length();
    Object obj = getLast(text, kind);
    int where = text.getSpanStart(obj);

    text.removeSpan(obj);

    if (where != len) {
        text.setSpan(repl, where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
    }
}

Pour utiliser, remplacez TagHandler comme ceci:

public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {

    if(tag.equalsIgnoreCase("strike") || tag.equals("s")) {

        if(opening){
            start((SpannableStringBuilder) output, new Strike();

        } else {
            end((SpannableStringBuilder) output, Strike.class, new StrikethroughSpan());
        }
    }       
}

/* 
 * Notice this class. It doesn't really do anything when it spans over the text. 
 * The reason is we just need to distinguish what needs to be spanned, then on our closing
 * tag, we will apply the spannable. For each of your different spannables you implement, just 
 * create a class here. 
 */
 private static class Strike{}
12
Chad Bingham

J'ai pris la réponse de Janoliver et j'ai trouvé ma version qui tente de prendre en charge plus d'options

        String text = ""; // HTML text to convert
        // Preprocessing phase to set up for HTML.fromHtml(...)
        text = text.replaceAll("<span style=\"(?:color: (#[a-fA-F\\d]{6})?; )?(?:font-family: (.*?); )?(?:font-size: (.*?);)? ?\">(.*?)</span>",
                               "<font color=\"$1\" face=\"$2\" size=\"$3\">$4</font>");
        text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )face=\"'(.*?)', .*?\"", "face=\"$1\"");
        text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"xx-small\"", "$1size=\"1\"");
        text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"x-small\"", "$1size=\"2\"");
        text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"small\"", "$1size=\"3\"");
        text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"medium\"", "$1size=\"4\"");
        text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"large\"", "$1size=\"5\"");
        text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"x-large\"", "$1size=\"6\"");
        text = text.replaceAll("(?<=<font color=\"#[a-fA-F0-9]{6}\" )(face=\".*?\" )size=\"xx-large\"", "$1size=\"7\"");
        text = text.replaceAll("<strong>(.*?)</strong>", "<_em>$1</_em>");  // we use strong for bold-face
        text = text.replaceAll("<em>(.*?)</em>", "<strong>$1</strong>");    // and em for italics
        text = text.replaceAll("<_em>(.*?)</_em>", "<em>$1</em>");          // but Android uses em for bold-face
        text = text.replaceAll("<span style=\"background-color: #([a-fA-F0-9]{6}).*?>(.*?)</span>", "<_$1>$2</_$1>");
        text_view.setText(Html.fromHtml(text, null, new Html.TagHandler() {
            private List<Object> _format_stack = new LinkedList<Object>();

            @Override
            public void handleTag(boolean open_tag, String tag, Editable output, XMLReader _) {
                if (tag.startsWith("ul"))
                    processBullet(open_tag, output);
                else if (tag.matches(".[a-fA-F0-9]{6}"))
                    processBackgroundColor(open_tag, output, tag.substring(1));
            }

            private void processBullet(boolean open_tag, Editable output) {
                final int length = output.length();
                if (open_tag) {
                    final Object format = new BulletSpan(BulletSpan.STANDARD_GAP_WIDTH);
                    _format_stack.add(format);
                    output.setSpan(format, length, length, Spanned.SPAN_MARK_MARK);
                } else {
                    applySpan(output, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                }
            }

            private void processBackgroundColor(boolean open_tag, Editable output, String color) {
                final int length = output.length();
                if (open_tag) {
                    final Object format = new BackgroundColorSpan(Color.parseColor('#' + color));
                    _format_stack.add(format);
                    output.setSpan(format, length, length, Spanned.SPAN_MARK_MARK);
                } else {
                    applySpan(output, length, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
                }
            }

            private Object getLast(Editable text, Class kind) {
                @SuppressWarnings("unchecked")
                final Object[] spans = text.getSpans(0, text.length(), kind);

                if (spans.length != 0)
                    for (int i = spans.length; i > 0; i--)
                        if (text.getSpanFlags(spans[i-1]) == Spannable.SPAN_MARK_MARK)
                            return spans[i-1];

                return null;
            }

            private void applySpan(Editable output, int length, int flags) {
                if (_format_stack.isEmpty()) return;

                final Object format = _format_stack.remove(0);
                final Object span = getLast(output, format.getClass());
                final int where = output.getSpanStart(span);

                output.removeSpan(span);

                if (where != length)
                    output.setSpan(format, where, length, flags);
            }
        }));

Cela semble obtenir les puces, la couleur de premier plan et la couleur d'arrière-plan. Cela peut fonctionner pour la police, mais vous devrez peut-être fournir les polices car il ne semble pas que Android prend en charge les polices autres que Droid/Roboto.

Il s'agit plus d'une preuve de concept, vous souhaiterez probablement convertir le regex en traitement String, car le regex ne prend en charge en aucune façon la combinaison du prétraitement, ce qui signifie que cela prend beaucoup de passes sur le String. Cela ne semble pas non plus changer la taille de la police, j'ai essayé de la définir comme "16sp", "medium" ou "4" sans voir les changements. Si quelqu'un a mis les tailles au travail, le partage d'esprit?

J'aimerais actuellement pouvoir ajouter un support de liste numérotée/ordonnée, c'est-à-dire.

  1. article
  2. article
  3. article

REMARQUE: Pour les personnes commençant par tout cela, il semble que la "balise" qui est donnée à handleTag(...) est juste le nom de la balise (comme "span"), et ne contient aucun des attributs attribués dans la balise (comme si vous aviez "), vous pouvez voir mon échappatoire autour de cela pour la couleur d'arrière-plan.

4
Dandre Allison

Nous développons en interne cette bibliothèque https://github.com/square1-io/rich-text-Android depuis un certain temps maintenant et nous l'utilisons dans un certain nombre d'applications d'actualités à forte intensité de contenu.

La bibliothèque peut analyser les balises html les plus courantes, y compris les vidéos et les images avec téléchargement à distance d'images. Une vue personnalisée, RichTextView peut ensuite être utilisée en remplacement de TextView pour afficher le contenu analysé.

Nous l'avons publié récemment, donc le document est encore incomplet, mais l'exemple fourni devrait être facile à suivre pour voir s'il correspond à vos besoins.

2
Roberto Prato

Bien que je puisse le voir dans l'API Html.Java, le style et l'alignement du texte devraient être utilisables avec les balises <p>, <div> etc. Je n'arrive pas à le faire fonctionner avec <p align="center"> ou <p style="text-align: center"> et de nombreuses autres variantes. Ne pouvant pas faire cet alignement central du texte et d'autres styles comme la taille de la police, plusieurs faces de police de mes fichiers ttf, la couleur d'arrière-plan, j'ai créé mon propre htmlTextView basé sur TextView mais avec ma propre classe tagHandler. Étant donné une ou deux irritations mineures, la plupart des balises sont très bien, mais mes balises d'alignement personnalisées, gauche , centre , droite ne fonctionne que dans des conditions spéciales (que je ne comprends pas), sinon. ils ne fonctionnent pas ou ne plantent pas l'application! Ceci est ma poignée de balise d'alignement. Il a la même structure que tous les autres gestionnaires de balises personnalisées mais se comporte vraiment bizarrement! La forme de base de mes gestionnaires de balises est la même et n'est pas conçue par moi ! J'ai trouvé le modèle de gestionnaire de balises après plusieurs heures de recherche sur le Web. Je suis reconnaissant à qui que ce soit qui l'a publié, mais ma mémoire et mes capacités organisationnelles sont telles que je ne me souviens pas vraiment qui ou où, donc si vous reconnaissez ce code comme le vôtre, faites-le moi savoir. Le seul lien (qui est ici) que j'ai est dans mon code: stackoverflow : Android: Comment utiliser le Html.TagHandler?

  private void ProcessAlignment(Layout.Alignment align, boolean opening, Editable output) {
    int len = output.length();
    if (opening) {
        output.setSpan(new AlignmentSpan.Standard(align), len, len, Spannable.SPAN_MARK_MARK);
    } else {
        Object obj = getLast(output, AlignmentSpan.Standard.class);
        int where = output.getSpanStart(obj);

        output.removeSpan(obj);

        if (where != len) {
            output.setSpan(new AlignmentSpan.Standard(align), where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
        }
    }
}

Je pense que le problème est que la balise de fin ne se connecte pas à la bonne balise de début.

private Object getLast(Editable text, Class kind) {
            Object[] objs = text.getSpans(0, text.length(), kind);

            if (objs.length == 0) {
                return null;
            } else {
                for (int i = objs.length - 1; i >= 0; --i) {
                    if (text.getSpanFlags(objs[i]) == Spannable.SPAN_MARK_MARK) {
                        return objs[i];
                    }
                }
                return null;
            }
        }

Ceci est la classe totale et quelque chose ne va pas. Le plus grand élément est ma compréhension! Peut-être que quelqu'un peut m'aider à mieux comprendre ...

public class htmlTextView extends AppCompatTextView {
static Typeface mLogo;
static Typeface mGAMZ;
static Typeface mChalk;
static Typeface mSouvenir;
int GS_PAINTFLAGS = FILTER_BITMAP_FLAG | ANTI_ALIAS_FLAG | SUBPIXEL_TEXT_FLAG | HINTING_ON;

public htmlTextView(Context context) {
    super(context);
    initialise();
}

public htmlTextView(Context context, @Nullable AttributeSet attrs) {
    super(context, attrs);
    initialise();
}

public htmlTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
    super(context, attrs, defStyleAttr);
    initialise();
}


private void initialise() {
    mLogo = Typeface.createFromAsset(theAssetManager, "fonts/logo.ttf");
    mGAMZ = Typeface.createFromAsset(theAssetManager, "fonts/GAMZ One.ttf");
    mChalk = Typeface.createFromAsset(theAssetManager, "fonts/swapfix.ttf");
    mSouvenir = Typeface.createFromAsset(theAssetManager, "fonts/Souvenir Regular.ttf");

    setPaintFlags(GS_PAINTFLAGS);
}

public void setDefaultTypefaceSouvenir() {
    setTypeface(mSouvenir);
}

public void setDefaultTypefaceGAMZ() {
    setTypeface(mGAMZ);
}

public void setDefaultTypefaceChalk() {
    setTypeface(mChalk);
}

/*public myTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
    super(context, attrs, defStyleAttr, defStyleRes);
}*/

public void setHTML(String htmltext) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { // Nougat API 24
        setText(Html.fromHtml(htmltext, Html.FROM_HTML_MODE_LEGACY,
                null, new TypefaceTagHandler()));
    } else {
        setText(Html.fromHtml(htmltext, null, new TypefaceTagHandler()));
    }
}

@Override
protected void dispatchDraw(Canvas canvas) {
    super.dispatchDraw(canvas);
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
}

@Override
public Bitmap getDrawingCache(boolean autoScale) {
    return super.getDrawingCache(autoScale);
}

@Override
public void draw(Canvas canvas) {
    super.draw(canvas);
}

// http://stackoverflow.com/questions/4044509/Android-how-to-use-the-html-taghandler
private static class TypefaceTagHandler implements Html.TagHandler {


    private void ProcessAlignment(Layout.Alignment align, boolean opening, Editable output) {
        int len = output.length();
        if (opening) {
            output.setSpan(new AlignmentSpan.Standard(align), len, len, Spannable.SPAN_MARK_MARK);
        } else {
            Object obj = getLast(output, AlignmentSpan.Standard.class);
            int where = output.getSpanStart(obj);

            output.removeSpan(obj);

            if (where != len) {
                output.setSpan(new AlignmentSpan.Standard(align), where, len, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
        }
    }

    private void ProcessTypefaceTag(Typeface tf, boolean opening, Editable output) {
        int len = output.length();
        if (opening) {
            output.setSpan(new CustomTypefaceSpan("", tf), len, len,
                    Spannable.SPAN_MARK_MARK);
        } else {
            Object obj = getLast(output, CustomTypefaceSpan.class);
            int where = output.getSpanStart(obj);

            output.removeSpan(obj);

            if (where != len) {
                output.setSpan(new CustomTypefaceSpan("", tf), where, len,
                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
        }
    }

    private void ProcessScaleTag(float scalefactor, boolean opening, Editable output) {
        int len = output.length();
        if (opening) {
            output.setSpan(new RelativeSizeSpan(scalefactor), len, len,
                    Spannable.SPAN_MARK_MARK);
        } else {
            Object obj = getLast(output, RelativeSizeSpan.class);
            int where = output.getSpanStart(obj);

            output.removeSpan(obj);

            if (where != len) {
                output.setSpan(new RelativeSizeSpan(scalefactor), where, len,
                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
        }
    }

    private void ProcessBox(int colour, boolean opening, Editable output) {
        int len = output.length();
        if (opening) {
            output.setSpan(new BackgroundColorSpan(colour), len, len,
                    Spannable.SPAN_MARK_MARK);
        } else {
            Object obj = getLast(output, BackgroundColorSpan.class);
            int where = output.getSpanStart(obj);

            output.removeSpan(obj);

            if (where != len) {
                output.setSpan(new BackgroundColorSpan(colour), where, len,
                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
        }
    }

    private void ProcessTextColour(int colour, boolean opening, Editable output) {
        int len = output.length();
        if (opening) {
            output.setSpan(new ForegroundColorSpan(colour), len, len,
                    Spannable.SPAN_MARK_MARK);
        } else {
            Object obj = getLast(output, ForegroundColorSpan.class);
            int where = output.getSpanStart(obj);

            output.removeSpan(obj);

            if (where != len) {
                output.setSpan(new ForegroundColorSpan(colour), where, len,
                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
            }
        }
    }

    final HashMap<String, String> attributes = new HashMap<>();

    @Override
    public void handleTag(boolean opening, String tag, Editable output, XMLReader xmlReader) {
        String Attr = "";
        //if (!opening) attributes.clear();
        processAttributes(xmlReader);

        if ("txt".equalsIgnoreCase(tag)) {
            Attr = attributes.get("clr");
            System.out.println("clr Attr: " + Attr + ", opening: " + opening);
            if (Attr == null || Attr.isEmpty()
                    || "black".equalsIgnoreCase(Attr)
                    || Attr.charAt(0) == 'k') {
                System.out.println("did black, opening: " + opening);
                ProcessTextColour(parseColor("#000000"), opening, output);
            } else {
                if (Attr.equalsIgnoreCase("g")) {
                    ProcessTextColour(parseColor("#b2b3b3"), opening, output);
                } else {
                    System.out.println("did colour, opening: " + opening);
                    ProcessTextColour(parseColor(Attr), opening, output);
                }
            }
            return;
        }

        if ("box".equalsIgnoreCase(tag)) {
            ProcessBox(parseColor("#d7d6d5"), opening, output);
            return;
        }


        if ("scl".equalsIgnoreCase(tag)) {
            Attr = attributes.get("fac");
            System.out.println("scl Attr: " + Attr);
            if (Attr != null && !Attr.isEmpty()) {
                ProcessScaleTag(parseFloat(Attr), opening, output);
            }
            return;
        }

        if ("left".equalsIgnoreCase(tag)) {
            ProcessAlignment(Layout.Alignment.ALIGN_NORMAL, opening, output);
            return;
        }

        if ("centre".equalsIgnoreCase(tag)) {
            ProcessAlignment(Layout.Alignment.ALIGN_CENTER, opening, output);
            return;
        }

        if ("right".equalsIgnoreCase(tag)) {
            ProcessAlignment(Layout.Alignment.ALIGN_OPPOSITE, opening, output);
            return;
        }

        if ("logo".equalsIgnoreCase(tag)) {
            ProcessTypefaceTag(mLogo, opening, output);
            return;
        }
        if ("gamz".equalsIgnoreCase(tag)) {
            ProcessTypefaceTag(mGAMZ, opening, output);
            return;
        }

        if ("chalk".equalsIgnoreCase(tag)) {
            System.out.println("chalk " + (opening ? "opening" : "closing"));
            ProcessTypefaceTag(mChalk, opening, output);
            return;
        }
    }

    private Object getLast(Editable text, Class kind) {
        Object[] objs = text.getSpans(0, text.length(), kind);

        if (objs.length == 0) {
            return null;
        } else {
            for (int i = objs.length - 1; i >= 0; --i) {
                if (text.getSpanFlags(objs[i]) == Spannable.SPAN_MARK_MARK) {
                    return objs[i];
                }
            }
            return null;
        }
    }

    private void processAttributes(final XMLReader xmlReader) {
        try {
            Field elementField = xmlReader.getClass().getDeclaredField("theNewElement");
            elementField.setAccessible(true);
            Object element = elementField.get(xmlReader);
            Field attsField = element.getClass().getDeclaredField("theAtts");
            attsField.setAccessible(true);
            Object atts = attsField.get(element);
            Field dataField = atts.getClass().getDeclaredField("data");
            dataField.setAccessible(true);
            String[] data = (String[])dataField.get(atts);
            Field lengthField = atts.getClass().getDeclaredField("length");
            lengthField.setAccessible(true);
            int len = (Integer)lengthField.get(atts);

            /**
             * MSH: Look for supported attributes and add to hash map.
             * This is as tight as things can get :)
             * The data index is "just" where the keys and values are stored.
             */
            for(int i = 0; i < len; i++)
                attributes.put(data[i * 5 + 1], data[i * 5 + 4]);
        }
        catch (Exception e) {
            Log.d(TAG, "Exception: " + e);
        }
    }

}

private static class CustomTypefaceSpan extends TypefaceSpan {
    private final Typeface newType;

    public CustomTypefaceSpan(String family, Typeface type) {
        super(family);
        newType = type;
    }

    @Override
    public void updateDrawState(TextPaint ds) {
        applyCustomTypeFace(ds, newType);
    }

    @Override
    public void updateMeasureState(TextPaint Paint) {
        applyCustomTypeFace(Paint, newType);
    }

    private void applyCustomTypeFace(Paint paint, Typeface tf) {
        int oldStyle;
        Typeface old = Paint.getTypeface();
        if (old == null) {
            oldStyle = 0;
        } else {
            oldStyle = old.getStyle();
        }

        int fake = oldStyle & ~tf.getStyle();
        if ((fake & Typeface.BOLD) != 0) {
            Paint.setFakeBoldText(true);
        }

        if ((fake & Typeface.ITALIC) != 0) {
            Paint.setTextSkewX(-0.25f);
        }
        Paint.setTypeface(tf);
    }
}

}

Le htmlTextView est créé à partir de l'activité avec:

 protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    theAssetManager = getAssets();
    htmlTextView tv = new htmlTextView(this);
    tv.setDefaultTypefaceSouvenir();
    tv.setTextColor(BLACK);
    tv.setBackgroundColor(0xfff0f0f0);
    tv.setPadding(4, 4, 4, 4);
    tv.setTextSize(30);
    tv.setMovementMethod(new ScrollingMovementMethod());
    tv.setHTML(getString(R.string.htmljumblies));
    //tv.setHTML(getString(R.string.htmltest));
    RelativeLayout rl = (RelativeLayout) findViewById(R.id.rl);
    rl.addView(tv);
}

et htmljumblies est défini dans strings.xml comme ci-dessous. Cette version particulière plantera l'application, mais si le premier <centre>, </centre> les balises sont supprimées des lignes 7 et 9, Les Jumblies apparaîtront centralisés? Confus et frustrant! Gardez-les et supprimez le <centre>, </centre> tags enveloppant Les Jumblies et rien ne se passe. La ligne dans l'en-tête n'est pas alignée au centre!

    <string name="htmljumblies">
    <![CDATA[&DoubleLongRightArrow;<logo><scl fac="1.1"><font color="#e5053a">GAMZ</font></scl></logo>
        <chalk><scl fac="1.8"> SWAP </scl></chalk>
        <scl fac="1.00">Set <b>1</b>, Game <b>1</b></scl>
        <br>
        <centre>
        <gamz><font color="#e5053a"><scl fac="1.50">a</scl></font><scl fac="0.90">(9)</scl></gamz>, <gamz><font color="#00a3dd"><scl fac="1.50">e</scl></font><scl fac="0.90">(8)</scl></gamz>, <gamz><font color="#fba311"><scl fac="1.50">i</scl></font><scl fac="0.90">(8)</scl></gamz>, <gamz><font color="#bc5e1e"><scl fac="1.50">o</scl></font><scl fac="0.90">(8)</scl></gamz>, <gamz><font color="#bf30b5"><scl fac="1.50">u</scl></font><scl fac="0.90">(9)</scl></gamz>
        </centre>
        <br>
        This is an example of my custom <b>htmlTextView</b> drawn from HTML format
        text with custom tags to use custom fonts, colouring typeface sizing and highlight boxes.
        The default font is <b><i>Souvenir</i></b>, but 3 other fonts are used:<br>
        The <font color="#e5053a"><b><logo><scl fac="1.1">GAMZ</scl></logo></b></font>
        <font color="#000080"><gamz><scl fac="0.8"><box>letter</box>
        <box>fonts</box><sc></gamz></font>
        and <chalk><scl fac="1.8">swapfix</scl></chalk>, essentially
        <chalk><scl fac="0.9">Staccato 555</scl></chalk>,
        as used in the words <chalk><scl fac="1.2">SWAP</scl></chalk> and
        <chalk><scl fac="1.2">FIX</scl></chalk>
        on the <font color="#e5053a"><b><logo><scl fac="1.1">GAMZ</scl></logo></b></font>
        boxes.
        <br>
        <centre>
        <scl fac="2"><box><b> <u>The Jumblies</u> </b></box></scl><br>
        <font color="#0000ff">
        They went to sea in a Sieve, they did,<br>
        In a Sieve they went to sea:<br>
        In spite of all their friends could say,<br>
        On a winter\'s morn, on a stormy day,<br>
        In a Sieve they went to sea!<br>
        And when the Sieve turned round and round,<br>
        And every one cried, \'You\'ll all be drowned!\'<br>
        They called aloud, \'Our Sieve ain\'t big,<br>
        But we don\'t care a button! we don\'t care a fig!<br>
        In a Sieve we\'ll go to sea!\'<br>
        Far and few, far and few,<br>
        Are the lands where the Jumblies live;<br>
        Their heads are green, and their hands are blue,<br>
        And they went to sea in a Sieve.<br>
        <br>
        They sailed away in a Sieve, they did,<br>
        In a Sieve they sailed so fast,<br>
        With only a beautiful pea-green veil<br>
        Tied with a riband by way of a sail,<br>
        To a small tobacco-pipe mast;<br>
        And every one said, who saw them go,<br>
        \'O won\'t they be soon upset, you know!<br>
        For the sky is dark, and the voyage is long,<br>
        And happen what may, it\'s extremely wrong<br>
        In a Sieve to sail so fast!\'<br>
        Far and few, far and few,<br>
        Are the lands where the Jumblies live;<br>
        Their heads are green, and their hands are blue,<br>
        And they went to sea in a Sieve.<br>
        <br>
        The water it soon came in, it did,<br>
        The water it soon came in;<br>
        So to keep them dry, they wrapped their feet<br>
        In a pinky paper all folded neat,<br>
        And they fastened it down with a pin.<br>
        And they passed the night in a crockery-jar,<br>
        And each of them said, \'How wise we are!<br>
        Though the sky be dark, and the voyage be long,<br>
        Yet we never can think we were rash or wrong,<br>
        While round in our Sieve we spin!\'<br>
        Far and few, far and few,<br>
        Are the lands where the Jumblies live;<br>
        Their heads are green, and their hands are blue,<br>
        And they went to sea in a Sieve.<br>
        <br>
        And all night long they sailed away;<br>
        And when the Sun went down,<br>
        They whistled and warbled a moony song<br>
        To the echoing sound of a coppery gong,<br>
        In the shade of the mountains brown.<br>
        \'O Timballo! How happy we are,<br>
        When we live in a Sieve and a crockery-jar,<br>
        And all night long in the moonlight pale,<br>
        We sail away with a pea-green sail,<br>
        In the shade of the mountains brown!\'<br>
        Far and few, far and few,<br>
        Are the lands where the Jumblies live;<br>
        Their heads are green, and their hands are blue,<br>
        And they went to sea in a Sieve.<br>
        <br>
        They sailed to the Western Sea, they did,<br>
        To a land all covered with trees,<br>
        And they bought an Owl, and a useful Cart,<br>
        And a pound of Rice, and a Cranberry Tart,<br>
        And a Hive of silvery Bees.<br>
        And they bought a Pig, and some green Jack-daws,<br>
        And a lovely Monkey with Lollipop paws,<br>
        And forty bottles of Ring-Bo-Ree,<br>
        And no end of Stilton Cheese.<br>
        Far and few, far and few,<br>
        Are the lands where the Jumblies live;<br>
        Their heads are green, and their hands are blue,<br>
        And they went to sea in a Sieve.<br>
        <br>
        And in twenty years they all came back,<br>
        In twenty years or more,<br>
        And every one said, \'How tall they\'ve grown!<br>
        For they\'ve been to the Lakes, and the Torrible Zone,<br>
        And the hills of the Chankly Bore!\'<br>
        And they drank their health, and gave them a feast<br>
        Of dumplings made of beautiful yeast;<br>
        And every one said, \'If we only live,<br>
        We too will go to sea in a Sieve,---<br>
        To the hills of the Chankly Bore!\'<br>
        Far and few, far and few,<br>
        Are the lands where the Jumblies live;<br>
        Their heads are green, and their hands are blue,<br>
        And they went to sea in a Sieve.</centre></font>
    ]]>
</string>
1
JosieH