代码之家  ›  专栏  ›  技术社区  ›  janoliver

Android:如何使用Html.TagHandler?

  •  43
  • janoliver  · 技术社区  · 15 年前

    现在,我搜索了很多,找不到这个类应该如何工作的例子。让我们考虑一下,我有一个u标记,用于在一些文本下面加下划线(我知道这是不推荐的,但不管怎样)。我的标记处理程序看起来怎么样?

    它的调用方式如下:

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

    前两个论点是好的。我想我必须使用output.append()修改输出。但是我该如何在这里加下划线呢?

    5 回复  |  直到 9 年前
        1
  •  101
  •   Atul    7 年前

    所以,我终于自己想出来了。

    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;
            }
        }
    
    
    }
    

    对于你的文本视图,你可以这样称呼:

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

    如果有人需要的话。

    干杯

        2
  •  12
  •   Klaas Richter Chad Bingham    10 年前

    此解决方案位于 Android sdk

    android.text.html . 第596-626行。复制/粘贴

    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);
        }
    }
    

    要使用,请重写标记处理程序,如下所示:

    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{}
    
        3
  •  4
  •   Dandre Allison    13 年前

    我接受了詹奥利弗的回答,提出了我的版本,试图支持更多的选择

            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);
                }
            }));
    

    这似乎得到了项目符号、前景色和背景色。它可能适用于字体字体,但您可能需要提供字体,因为Android似乎不支持Droid/Roboto以外的字体。

    这更像是概念的证明,您可能希望将regex转换为 String 处理,因为regex不支持以任何方式组合预处理,这意味着在 字符串 . 这似乎也不能改变字体大小,我试着定义为“16sp”、“medium”或“4”而没有看到改变。如果有人已经有尺寸的工作,介意分享吗?

    我目前希望能够添加编号/顺序列表支持,即。

    1. 项目
    2. 项目

    注: handleTag(...) 只是标记的名称(如“span”),不包含标记中指定的任何属性(如有),您可以看到我在背景色方面的漏洞。

        4
  •  2
  •   Roberto Prato    8 年前

    我们一直在内部开发这个图书馆 https://github.com/square1-io/rich-text-android

    该库可以解析最常见的html标记,包括视频和img,并可以远程下载图像。 然后可以使用自定义视图RichTextView替换TextView来显示解析的内容。

    我们刚刚公开发布了它,所以该文件仍然是不完整的,但提供的例子应该很容易遵循,看看它是否适合您的需要。

        5
  •  1
  •   JosieH    8 年前

    尽管我可以在Html.java API中看到,样式和文本对齐应该可以用于标记 <p> <div> 等等,我没能让它工作 <p align="center"> <p style="text-align: center"> 以及许多其他变体。由于无法对文本和其他样式(如字体大小、来自ttf文件的多个字体面、背景颜色)进行这种居中对齐,我基于TextView创建了自己的htmlTextView,但使用了自己的tagHandler类。如果有一两个小问题,大多数标签都很好,但我的自定义对齐标签, 左边 , 中心 正确的 只在特殊情况下工作(我不明白),否则。他们不工作或崩溃的应用程序!这是我的对齐标记句柄。它与所有其他自定义标记处理程序具有相同的结构,但其行为非常怪异!我的标记处理程序的基本形式是相同的 ! 在网上搜索了几个小时后,我找到了taghandler模板。不管是谁发的,我都很感激,但我的记忆和组织能力让我记不清是谁或在哪里发的,所以如果你认为这是你的代码,请告诉我。我唯一的链接(在这里)是在我的代码中: stackoverflow : Android: How to use the 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 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;
                }
            }
    

    这是全班的,有些事情不对。最大的组成部分是我的理解!也许有人能帮我更好地理解。。。

    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);
        }
    }
    

    }

    htmlTextView是从以下活动创建的:

     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);
    }
    

    超大型 在strings.xml中定义如下。此特定版本将使应用程序崩溃,但如果第一个 <centre> , </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>