Skip to content
This repository has been archived by the owner on Sep 7, 2020. It is now read-only.

Commit

Permalink
Html Tag Handler can handle basic tables by storing the HTML to be la…
Browse files Browse the repository at this point in the history
…ter used by a ClickTableSpan. Applications wanting to take advantage of this capability need to implement a ClickTableSpan and do whatever they'd like with the raw HTML (i.e. pass it into a webview).

In addition, a DrawTableSpan has been provided for applications that want to display text other than "[tap for table]", which should be useful for localization.
  • Loading branch information
seato committed Feb 23, 2016
1 parent e14a2b8 commit d77bf1f
Show file tree
Hide file tree
Showing 11 changed files with 380 additions and 15 deletions.
6 changes: 3 additions & 3 deletions HtmlTextView/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,16 @@ android {
defaultConfig {
minSdkVersion 7
targetSdkVersion 22
versionCode 4
versionName '1.3'
versionCode 5
versionName '1.4'
}
}

publish {
userOrg = 'sufficientlysecure'
groupId = 'org.sufficientlysecure'
artifactId = 'html-textview'
version = '1.3'
version = '1.4'
description = 'HtmlTextView is an extended TextView component for Android, which can load HTML and converts it into Spannable for displaying it.'
website = 'https://github.com/sufficientlysecure/html-textview'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright (C) 2016 Richard Thai
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.sufficientlysecure.htmltextview;

import android.text.style.ClickableSpan;

/**
* This span defines what should happen if a table is clicked. This abstract class is defined so
* that applications can access the raw table HTML and do whatever they'd like to render it (e.g.
* show it in a WebView).
*/
public abstract class ClickableTableSpan extends ClickableSpan {
protected String mTableHtml;

// This sucks, but we need this so that each table can get its own ClickableTableSpan.
// Otherwise, we end up removing the clicking from earlier tables.
public abstract ClickableTableSpan newInstance();

public void setTableHtml(String tableHtml) {
this.mTableHtml = tableHtml;
}

public String getTableHtml() {
return mTableHtml;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright (C) 2016 Richard Thai
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.sufficientlysecure.htmltextview;

import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.text.style.ReplacementSpan;

/**
* This span defines how a table should be rendered in the HtmlTextView. The default implementation
* is a cop-out which replaces the HTML table with some text ("[tap for table]" is the default).
*
* This is to be used in conjunction with the ClickableTableSpan which will redirect a click to the
* text some application-defined action (i.e. render the raw HTML in a WebView).
*/
public class DrawTableLinkSpan extends ReplacementSpan {
private int mWidth;

private static final String DEFAULT_TABLE_LINK_TEXT = "[tap for table]";
private static float DEFAULT_TEXT_SIZE = 80f;
private static int DEFAULT_TEXT_COLOR = Color.BLUE;

protected String mTableLinkText = DEFAULT_TABLE_LINK_TEXT;
protected float mTextSize = DEFAULT_TEXT_SIZE;
protected int mTextColor = DEFAULT_TEXT_COLOR;

@Override
public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
mWidth = (int) paint.measureText(mTableLinkText, 0, mTableLinkText.length());
mTextSize = paint.getTextSize();
return mWidth;
}

@Override
public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
final Paint paint2 = new Paint();
paint2.setStyle(Paint.Style.STROKE);
paint2.setColor(Color.BLUE);
paint2.setAntiAlias(true);
paint2.setTextSize(mTextSize);

canvas.drawText(mTableLinkText, x, bottom, paint2);
}

public void setTableLinkText(String tableLinkText) {
this.mTableLinkText = tableLinkText;
}

public void setTextSize(float textSize) {
this.mTextSize = textSize;
}

public void setTextColor(int textColor) {
this.mTextColor = textColor;
}

public String getTableLinkText() {
return mTableLinkText;
}

public float getTextSize() {
return mTextSize;
}

public int getTextColor() {
return mTextColor;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,30 @@ public class HtmlTagHandler implements Html.TagHandler {
/**
* List indentation in pixels. Nested lists use multiple of this.
*/
/**
* Running HTML table string based off of the root table tag. Root table tag being the tag which
* isn't embedded within any other table tag. Example:
* <!-- This is the root level opening table tag. This is where we keep track of tables. -->
* <table>
* ...
* <table> <!-- Non-root table tags -->
* ...
* </table>
* ...
* </table>
* <!-- This is the root level closing table tag and the end of the string we track. -->
*/
StringBuilder tableHtmlBuilder = new StringBuilder();
/**
* Tells us which level of table tag we're on; ultimately used to find the root table tag.
*/
int tableTagLevel = 0;

private static final int indent = 10;
private static final int listItemIndent = indent * 2;
private static final BulletSpan bullet = new BulletSpan(indent);
private ClickableTableSpan mClickableTableSpan;
private DrawTableLinkSpan mDrawTableLinkSpan;

private static class Ul {
}
Expand All @@ -71,6 +92,18 @@ private static class Center {
private static class Strike {
}

private static class Table {
}

private static class Tr {
}

private static class Th {
}

private static class Td {
}

@Override
public void handleTag(final boolean opening, final String tag, Editable output, final XMLReader xmlReader) {
if (opening) {
Expand Down Expand Up @@ -102,6 +135,23 @@ public void handleTag(final boolean opening, final String tag, Editable output,
start(output, new Center());
} else if (tag.equalsIgnoreCase("s") || tag.equalsIgnoreCase("strike")) {
start(output, new Strike());
} else if (tag.equalsIgnoreCase("table")) {
start(output, new Table());
if (tableTagLevel == 0) {
tableHtmlBuilder = new StringBuilder();
// We need some text for the table to be replaced by the span because
// the other tags will remove their text when their text is extracted
output.append("table placeholder");
}

tableTagLevel++;
}
else if (tag.equalsIgnoreCase("tr")) {
start(output, new Tr());
} else if (tag.equalsIgnoreCase("th")) {
start(output, new Th());
} else if (tag.equalsIgnoreCase("td")) {
start(output, new Td());
}
} else {
// closing tag
Expand Down Expand Up @@ -150,8 +200,46 @@ public void handleTag(final boolean opening, final String tag, Editable output,
end(output, Center.class, true, new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER));
} else if (tag.equalsIgnoreCase("s") || tag.equalsIgnoreCase("strike")) {
end(output, Strike.class, false, new StrikethroughSpan());
} else if (tag.equalsIgnoreCase("table")) {
tableTagLevel--;

// When we're back at the root-level table
if (tableTagLevel == 0) {
final String tableHtml = tableHtmlBuilder.toString();
ClickableTableSpan clickableTableSpan = mClickableTableSpan.newInstance();
clickableTableSpan.setTableHtml(tableHtml);
end(output, Table.class, false, new DrawTableLinkSpan(), clickableTableSpan);
} else {
end(output, Table.class, false);
}
}
else if (tag.equalsIgnoreCase("tr")) {
end(output, Tr.class, false);
} else if (tag.equalsIgnoreCase("th")) {
end(output, Th.class, false);
} else if (tag.equalsIgnoreCase("td")) {
end(output, Td.class, false);

}
}

storeTableTags(opening, tag);
}

/**
* If we're arriving at a table tag or are already within a table tag, then we should store it
* the raw HTML for our ClickableTableSpan
*/
private void storeTableTags(boolean opening, String tag) {
if (tableTagLevel > 0 || tag.equalsIgnoreCase("table")) {
tableHtmlBuilder.append("<");
if (!opening) {
tableHtmlBuilder.append("/");
}
tableHtmlBuilder
.append(tag.toLowerCase())
.append(">");
}
}

/**
Expand All @@ -176,6 +264,12 @@ private void end(Editable output, Class kind, boolean paragraphStyle, Object...
// end of the tag
int len = output.length();

// If we're in a table, then we need to store the raw HTML for later
if (tableTagLevel > 0) {
final CharSequence extractedSpanText = extractSpanText(output, kind);
tableHtmlBuilder.append(extractedSpanText);
}

output.removeSpan(obj);

if (where != len) {
Expand All @@ -196,6 +290,21 @@ private void end(Editable output, Class kind, boolean paragraphStyle, Object...
}
}

/**
* Returns the text contained within a span and deletes it from the output string
*/
private CharSequence extractSpanText(Editable output, Class kind) {
final Object obj = getLast(output, kind);
// start of the tag
final int where = output.getSpanStart(obj);
// end of the tag
final int len = output.length();

final CharSequence extractedSpanText = output.subSequence(where, len);
output.delete(where, len);
return extractedSpanText;
}

/**
* Get last marked position of a specific tag kind (private class)
*/
Expand All @@ -213,4 +322,11 @@ private static Object getLast(Editable text, Class kind) {
}
}

public void setClickableTableSpan(ClickableTableSpan clickableTableSpan) {
this.mClickableTableSpan = clickableTableSpan;
}

public void setDrawTableLinkSpan(DrawTableLinkSpan drawTableLinkSpan) {
this.mDrawTableLinkSpan = drawTableLinkSpan;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public class HtmlTextView extends JellyBeanSpanFixTextView {
public static final boolean DEBUG = false;
boolean mDontConsumeNonUrlClicks = true;
boolean mLinkHit;
private ClickableTableSpan mClickableTableSpan;
private DrawTableLinkSpan mDrawTableLinkSpan;

public HtmlTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
Expand Down Expand Up @@ -113,13 +115,13 @@ public void setHtmlFromString(String html, ImageGetter imageGetter) {
}

// this uses Android's Html class for basic parsing, and HtmlTagHandler
setText(Html.fromHtml(html, htmlImageGetter, new HtmlTagHandler()));
final HtmlTagHandler htmlTagHandler = new HtmlTagHandler();
htmlTagHandler.setClickableTableSpan(mClickableTableSpan);
htmlTagHandler.setDrawTableLinkSpan(mDrawTableLinkSpan);
setText(Html.fromHtml(html, htmlImageGetter, htmlTagHandler));

// make links work
setMovementMethod(LocalLinkMovementMethod.getInstance());

// no flickering when clicking textview for Android < 4, but overriders color...
// text.setTextColor(getResources().getColor(android.R.color.secondary_text_dark_nodisable));
}

/**
Expand All @@ -144,4 +146,11 @@ public void setHtmlFromString(String html, boolean useLocalDrawables) {
}
}

public void setClickableTableSpan(ClickableTableSpan clickableTableSpan) {
this.mClickableTableSpan = clickableTableSpan;
}

public void setDrawTableLinkSpan(DrawTableLinkSpan drawTableLinkSpan) {
this.mDrawTableLinkSpan = drawTableLinkSpan;
}
}
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ repositories {
}
dependencies {
compile 'org.sufficientlysecure:html-textview:1.3'
compile 'org.sufficientlysecure:html-textview:1.4'
}
```

Expand Down Expand Up @@ -88,6 +88,10 @@ text.setHtmlFromRawResource(this, R.raw.help, new RemoteImageGetter());
* ``<code>``
* ``<center>``
* ``<strike>``
* ``<table>``
* ``<tr>``
* ``<th>``
* ``<td>``

## License
Apache License v2
Expand All @@ -100,6 +104,7 @@ See LICENSE for full license text.
- Original [HtmlRemoteImageGetter](https://gist.github.com/Antarix/4167655) developed by Antarix Tandon
- Original [HtmlLocalImageGetter](http://stackoverflow.com/a/22298833) developed by drawk
- [JellyBeanSpanFixTextView](https://gist.github.com/pyricau/3424004) (with fix from comment) developed by Pierre-Yves Ricau
- [Table support](https://github.com/SufficientlySecure/html-textview/pull/33) added by Richard Thai

## Contributions

Expand Down

0 comments on commit d77bf1f

Please sign in to comment.