0

I'm stumped, confused and frustrated! Although I can see It in Html.java API that style and text-align should be usable with the tags <p> , <div> etc. I am failing to get it to work with <p align="center"> or <p style="text-align: center"> and many other variants.

What is the correct form for use with Html.java through the tagHandler in the fromHTML() method?

The received knowledge is that text alignment is not possible with preset taghandlers in Html.java, but, if so, why is there code therein that looks capable of handling it along with other CSS style parameters?

Why doesn't the custom tagHandler I am using for text alignment work? Read on for more details of my test app...

Not being able to do this central alignment of text, and other styles like font size, multiple font faces from my ttf files, background colour, I have made my own htmlTextView based on TextView but with a custom tagHandler class. Given one or two minor irritations, most of the tags are fine but my custom alignment tags, left, centre, right work only in special conditions (that I don't understand), otherwise. they don't work or crash the app! This is my alignment tag handle. It has the same structure as all the other custom tag handlers but really behaves weirdly! However, to be honest, none of them work 100%. A notable oddity is having to use a text character at the very start of the HTML text before using a tag to invoke my custom font!!

The basic form of all the tag handlers are the same and I will confess are not conceived by me! I found a taghandler template after many hours searching on the web. I am grateful to whoever it was that posted it but my memory and organisational ability are such that I can't really remember who or where, so if you recognise this code as yours please let me know. The only link (which is here) I have is in my code: stackoverflow: Android: How to use the Html.TagHandler? @Dandre Allison

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

I think the problem is that the end tag is not getting connected to the correct start tag.

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

This is the total class and something is not right. The largest component is my lack of understanding! Perhaps someone can help me understand better...

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

}

The htmlTextView is created from the activity with:

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

and htmljumblies is defined in strings.xml as below. This particular version will crash the app but if the first <centre>, </centre> tags are removed from lines 7 and 9, The Jumblies will appear centralised? Confusing and frustrating! Keep them and remove the <centre>, </centre> tags enfolding The Jumblies and nothing happens. The line in the header is not centrally aligned!

    <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>
Glorfindel
  • 21,988
  • 13
  • 81
  • 109
JosieH
  • 85
  • 10
  • I now think I should quit the HTML route to a formatted textview and instead deal with the spans directly. I thought the HTML method would make life easier but it is no proving to be so... :( – JosieH Jul 28 '17 at 00:08
  • In fact, by watching the way tags are sent to my tag handler as the open and close, I am totally NOT convinced Html.java is sending me the right tags in the right order. It is inconsistent and unreliable. I might be fault in the way I am handling the tags as they are sent to me but ... – JosieH Jul 28 '17 at 12:14

0 Answers0