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

Commit d77bf1f

Browse files
committed
Html Tag Handler can handle basic tables by storing the HTML to be later 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.
1 parent e14a2b8 commit d77bf1f

File tree

11 files changed

+380
-15
lines changed

11 files changed

+380
-15
lines changed

HtmlTextView/build.gradle

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,16 @@ android {
88
defaultConfig {
99
minSdkVersion 7
1010
targetSdkVersion 22
11-
versionCode 4
12-
versionName '1.3'
11+
versionCode 5
12+
versionName '1.4'
1313
}
1414
}
1515

1616
publish {
1717
userOrg = 'sufficientlysecure'
1818
groupId = 'org.sufficientlysecure'
1919
artifactId = 'html-textview'
20-
version = '1.3'
20+
version = '1.4'
2121
description = 'HtmlTextView is an extended TextView component for Android, which can load HTML and converts it into Spannable for displaying it.'
2222
website = 'https://github.com/sufficientlysecure/html-textview'
2323
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
/*
2+
* Copyright (C) 2016 Richard Thai
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.sufficientlysecure.htmltextview;
18+
19+
import android.text.style.ClickableSpan;
20+
21+
/**
22+
* This span defines what should happen if a table is clicked. This abstract class is defined so
23+
* that applications can access the raw table HTML and do whatever they'd like to render it (e.g.
24+
* show it in a WebView).
25+
*/
26+
public abstract class ClickableTableSpan extends ClickableSpan {
27+
protected String mTableHtml;
28+
29+
// This sucks, but we need this so that each table can get its own ClickableTableSpan.
30+
// Otherwise, we end up removing the clicking from earlier tables.
31+
public abstract ClickableTableSpan newInstance();
32+
33+
public void setTableHtml(String tableHtml) {
34+
this.mTableHtml = tableHtml;
35+
}
36+
37+
public String getTableHtml() {
38+
return mTableHtml;
39+
}
40+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
/*
2+
* Copyright (C) 2016 Richard Thai
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.sufficientlysecure.htmltextview;
18+
19+
import android.graphics.Canvas;
20+
import android.graphics.Color;
21+
import android.graphics.Paint;
22+
import android.text.style.ReplacementSpan;
23+
24+
/**
25+
* This span defines how a table should be rendered in the HtmlTextView. The default implementation
26+
* is a cop-out which replaces the HTML table with some text ("[tap for table]" is the default).
27+
*
28+
* This is to be used in conjunction with the ClickableTableSpan which will redirect a click to the
29+
* text some application-defined action (i.e. render the raw HTML in a WebView).
30+
*/
31+
public class DrawTableLinkSpan extends ReplacementSpan {
32+
private int mWidth;
33+
34+
private static final String DEFAULT_TABLE_LINK_TEXT = "[tap for table]";
35+
private static float DEFAULT_TEXT_SIZE = 80f;
36+
private static int DEFAULT_TEXT_COLOR = Color.BLUE;
37+
38+
protected String mTableLinkText = DEFAULT_TABLE_LINK_TEXT;
39+
protected float mTextSize = DEFAULT_TEXT_SIZE;
40+
protected int mTextColor = DEFAULT_TEXT_COLOR;
41+
42+
@Override
43+
public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
44+
mWidth = (int) paint.measureText(mTableLinkText, 0, mTableLinkText.length());
45+
mTextSize = paint.getTextSize();
46+
return mWidth;
47+
}
48+
49+
@Override
50+
public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
51+
final Paint paint2 = new Paint();
52+
paint2.setStyle(Paint.Style.STROKE);
53+
paint2.setColor(Color.BLUE);
54+
paint2.setAntiAlias(true);
55+
paint2.setTextSize(mTextSize);
56+
57+
canvas.drawText(mTableLinkText, x, bottom, paint2);
58+
}
59+
60+
public void setTableLinkText(String tableLinkText) {
61+
this.mTableLinkText = tableLinkText;
62+
}
63+
64+
public void setTextSize(float textSize) {
65+
this.mTextSize = textSize;
66+
}
67+
68+
public void setTextColor(int textColor) {
69+
this.mTextColor = textColor;
70+
}
71+
72+
public String getTableLinkText() {
73+
return mTableLinkText;
74+
}
75+
76+
public float getTextSize() {
77+
return mTextSize;
78+
}
79+
80+
public int getTextColor() {
81+
return mTextColor;
82+
}
83+
}

HtmlTextView/src/main/java/org/sufficientlysecure/htmltextview/HtmlTagHandler.java

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,30 @@ public class HtmlTagHandler implements Html.TagHandler {
5252
/**
5353
* List indentation in pixels. Nested lists use multiple of this.
5454
*/
55+
/**
56+
* Running HTML table string based off of the root table tag. Root table tag being the tag which
57+
* isn't embedded within any other table tag. Example:
58+
* <!-- This is the root level opening table tag. This is where we keep track of tables. -->
59+
* <table>
60+
* ...
61+
* <table> <!-- Non-root table tags -->
62+
* ...
63+
* </table>
64+
* ...
65+
* </table>
66+
* <!-- This is the root level closing table tag and the end of the string we track. -->
67+
*/
68+
StringBuilder tableHtmlBuilder = new StringBuilder();
69+
/**
70+
* Tells us which level of table tag we're on; ultimately used to find the root table tag.
71+
*/
72+
int tableTagLevel = 0;
73+
5574
private static final int indent = 10;
5675
private static final int listItemIndent = indent * 2;
5776
private static final BulletSpan bullet = new BulletSpan(indent);
77+
private ClickableTableSpan mClickableTableSpan;
78+
private DrawTableLinkSpan mDrawTableLinkSpan;
5879

5980
private static class Ul {
6081
}
@@ -71,6 +92,18 @@ private static class Center {
7192
private static class Strike {
7293
}
7394

95+
private static class Table {
96+
}
97+
98+
private static class Tr {
99+
}
100+
101+
private static class Th {
102+
}
103+
104+
private static class Td {
105+
}
106+
74107
@Override
75108
public void handleTag(final boolean opening, final String tag, Editable output, final XMLReader xmlReader) {
76109
if (opening) {
@@ -102,6 +135,23 @@ public void handleTag(final boolean opening, final String tag, Editable output,
102135
start(output, new Center());
103136
} else if (tag.equalsIgnoreCase("s") || tag.equalsIgnoreCase("strike")) {
104137
start(output, new Strike());
138+
} else if (tag.equalsIgnoreCase("table")) {
139+
start(output, new Table());
140+
if (tableTagLevel == 0) {
141+
tableHtmlBuilder = new StringBuilder();
142+
// We need some text for the table to be replaced by the span because
143+
// the other tags will remove their text when their text is extracted
144+
output.append("table placeholder");
145+
}
146+
147+
tableTagLevel++;
148+
}
149+
else if (tag.equalsIgnoreCase("tr")) {
150+
start(output, new Tr());
151+
} else if (tag.equalsIgnoreCase("th")) {
152+
start(output, new Th());
153+
} else if (tag.equalsIgnoreCase("td")) {
154+
start(output, new Td());
105155
}
106156
} else {
107157
// closing tag
@@ -150,8 +200,46 @@ public void handleTag(final boolean opening, final String tag, Editable output,
150200
end(output, Center.class, true, new AlignmentSpan.Standard(Layout.Alignment.ALIGN_CENTER));
151201
} else if (tag.equalsIgnoreCase("s") || tag.equalsIgnoreCase("strike")) {
152202
end(output, Strike.class, false, new StrikethroughSpan());
203+
} else if (tag.equalsIgnoreCase("table")) {
204+
tableTagLevel--;
205+
206+
// When we're back at the root-level table
207+
if (tableTagLevel == 0) {
208+
final String tableHtml = tableHtmlBuilder.toString();
209+
ClickableTableSpan clickableTableSpan = mClickableTableSpan.newInstance();
210+
clickableTableSpan.setTableHtml(tableHtml);
211+
end(output, Table.class, false, new DrawTableLinkSpan(), clickableTableSpan);
212+
} else {
213+
end(output, Table.class, false);
214+
}
215+
}
216+
else if (tag.equalsIgnoreCase("tr")) {
217+
end(output, Tr.class, false);
218+
} else if (tag.equalsIgnoreCase("th")) {
219+
end(output, Th.class, false);
220+
} else if (tag.equalsIgnoreCase("td")) {
221+
end(output, Td.class, false);
222+
153223
}
154224
}
225+
226+
storeTableTags(opening, tag);
227+
}
228+
229+
/**
230+
* If we're arriving at a table tag or are already within a table tag, then we should store it
231+
* the raw HTML for our ClickableTableSpan
232+
*/
233+
private void storeTableTags(boolean opening, String tag) {
234+
if (tableTagLevel > 0 || tag.equalsIgnoreCase("table")) {
235+
tableHtmlBuilder.append("<");
236+
if (!opening) {
237+
tableHtmlBuilder.append("/");
238+
}
239+
tableHtmlBuilder
240+
.append(tag.toLowerCase())
241+
.append(">");
242+
}
155243
}
156244

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

267+
// If we're in a table, then we need to store the raw HTML for later
268+
if (tableTagLevel > 0) {
269+
final CharSequence extractedSpanText = extractSpanText(output, kind);
270+
tableHtmlBuilder.append(extractedSpanText);
271+
}
272+
179273
output.removeSpan(obj);
180274

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

293+
/**
294+
* Returns the text contained within a span and deletes it from the output string
295+
*/
296+
private CharSequence extractSpanText(Editable output, Class kind) {
297+
final Object obj = getLast(output, kind);
298+
// start of the tag
299+
final int where = output.getSpanStart(obj);
300+
// end of the tag
301+
final int len = output.length();
302+
303+
final CharSequence extractedSpanText = output.subSequence(where, len);
304+
output.delete(where, len);
305+
return extractedSpanText;
306+
}
307+
199308
/**
200309
* Get last marked position of a specific tag kind (private class)
201310
*/
@@ -213,4 +322,11 @@ private static Object getLast(Editable text, Class kind) {
213322
}
214323
}
215324

325+
public void setClickableTableSpan(ClickableTableSpan clickableTableSpan) {
326+
this.mClickableTableSpan = clickableTableSpan;
327+
}
328+
329+
public void setDrawTableLinkSpan(DrawTableLinkSpan drawTableLinkSpan) {
330+
this.mDrawTableLinkSpan = drawTableLinkSpan;
331+
}
216332
}

HtmlTextView/src/main/java/org/sufficientlysecure/htmltextview/HtmlTextView.java

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@ public class HtmlTextView extends JellyBeanSpanFixTextView {
3131
public static final boolean DEBUG = false;
3232
boolean mDontConsumeNonUrlClicks = true;
3333
boolean mLinkHit;
34+
private ClickableTableSpan mClickableTableSpan;
35+
private DrawTableLinkSpan mDrawTableLinkSpan;
3436

3537
public HtmlTextView(Context context, AttributeSet attrs, int defStyle) {
3638
super(context, attrs, defStyle);
@@ -113,13 +115,13 @@ public void setHtmlFromString(String html, ImageGetter imageGetter) {
113115
}
114116

115117
// this uses Android's Html class for basic parsing, and HtmlTagHandler
116-
setText(Html.fromHtml(html, htmlImageGetter, new HtmlTagHandler()));
118+
final HtmlTagHandler htmlTagHandler = new HtmlTagHandler();
119+
htmlTagHandler.setClickableTableSpan(mClickableTableSpan);
120+
htmlTagHandler.setDrawTableLinkSpan(mDrawTableLinkSpan);
121+
setText(Html.fromHtml(html, htmlImageGetter, htmlTagHandler));
117122

118123
// make links work
119124
setMovementMethod(LocalLinkMovementMethod.getInstance());
120-
121-
// no flickering when clicking textview for Android < 4, but overriders color...
122-
// text.setTextColor(getResources().getColor(android.R.color.secondary_text_dark_nodisable));
123125
}
124126

125127
/**
@@ -144,4 +146,11 @@ public void setHtmlFromString(String html, boolean useLocalDrawables) {
144146
}
145147
}
146148

149+
public void setClickableTableSpan(ClickableTableSpan clickableTableSpan) {
150+
this.mClickableTableSpan = clickableTableSpan;
151+
}
152+
153+
public void setDrawTableLinkSpan(DrawTableLinkSpan drawTableLinkSpan) {
154+
this.mDrawTableLinkSpan = drawTableLinkSpan;
155+
}
147156
}

README.md

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ repositories {
1818
}
1919
2020
dependencies {
21-
compile 'org.sufficientlysecure:html-textview:1.3'
21+
compile 'org.sufficientlysecure:html-textview:1.4'
2222
}
2323
```
2424

@@ -88,6 +88,10 @@ text.setHtmlFromRawResource(this, R.raw.help, new RemoteImageGetter());
8888
* ``<code>``
8989
* ``<center>``
9090
* ``<strike>``
91+
* ``<table>``
92+
* ``<tr>``
93+
* ``<th>``
94+
* ``<td>``
9195

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

104109
## Contributions
105110

0 commit comments

Comments
 (0)