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

Commit 17972e2

Browse files
author
Dominik Schürmann
committed
Merge pull request #33 from seato/html-table-support
Rudimentary table support
2 parents e14a2b8 + 7baea7b commit 17972e2

File tree

11 files changed

+418
-15
lines changed

11 files changed

+418
-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: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
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 = "";
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+
// This sucks, but we need this so that each table can get drawn.
43+
// Otherwise, we end up with the default table link text (nothing) for earlier tables.
44+
public DrawTableLinkSpan newInstance() {
45+
final DrawTableLinkSpan drawTableLinkSpan = new DrawTableLinkSpan();
46+
drawTableLinkSpan.setTableLinkText(mTableLinkText);
47+
drawTableLinkSpan.setTextSize(mTextSize);
48+
drawTableLinkSpan.setTextColor(mTextColor);
49+
50+
return drawTableLinkSpan;
51+
}
52+
53+
@Override
54+
public int getSize(Paint paint, CharSequence text, int start, int end, Paint.FontMetricsInt fm) {
55+
mWidth = (int) paint.measureText(mTableLinkText, 0, mTableLinkText.length());
56+
mTextSize = paint.getTextSize();
57+
return mWidth;
58+
}
59+
60+
@Override
61+
public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, int y, int bottom, Paint paint) {
62+
final Paint paint2 = new Paint();
63+
paint2.setStyle(Paint.Style.STROKE);
64+
paint2.setColor(mTextColor);
65+
paint2.setAntiAlias(true);
66+
paint2.setTextSize(mTextSize);
67+
68+
canvas.drawText(mTableLinkText, x, bottom, paint2);
69+
}
70+
71+
public void setTableLinkText(String tableLinkText) {
72+
this.mTableLinkText = tableLinkText;
73+
}
74+
75+
public void setTextSize(float textSize) {
76+
this.mTextSize = textSize;
77+
}
78+
79+
public void setTextColor(int textColor) {
80+
this.mTextColor = textColor;
81+
}
82+
83+
public String getTableLinkText() {
84+
return mTableLinkText;
85+
}
86+
87+
public float getTextSize() {
88+
return mTextSize;
89+
}
90+
91+
public int getTextColor() {
92+
return mTextColor;
93+
}
94+
}

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

Lines changed: 125 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,55 @@ 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+
210+
ClickableTableSpan clickableTableSpan = null;
211+
if (mClickableTableSpan != null) {
212+
clickableTableSpan = mClickableTableSpan.newInstance();
213+
clickableTableSpan.setTableHtml(tableHtml);
214+
}
215+
216+
DrawTableLinkSpan drawTableLinkSpan = null;
217+
if (mDrawTableLinkSpan != null) {
218+
drawTableLinkSpan = mDrawTableLinkSpan.newInstance();
219+
}
220+
221+
end(output, Table.class, false, drawTableLinkSpan, clickableTableSpan);
222+
} else {
223+
end(output, Table.class, false);
224+
}
225+
}
226+
else if (tag.equalsIgnoreCase("tr")) {
227+
end(output, Tr.class, false);
228+
} else if (tag.equalsIgnoreCase("th")) {
229+
end(output, Th.class, false);
230+
} else if (tag.equalsIgnoreCase("td")) {
231+
end(output, Td.class, false);
153232
}
154233
}
234+
235+
storeTableTags(opening, tag);
236+
}
237+
238+
/**
239+
* If we're arriving at a table tag or are already within a table tag, then we should store it
240+
* the raw HTML for our ClickableTableSpan
241+
*/
242+
private void storeTableTags(boolean opening, String tag) {
243+
if (tableTagLevel > 0 || tag.equalsIgnoreCase("table")) {
244+
tableHtmlBuilder.append("<");
245+
if (!opening) {
246+
tableHtmlBuilder.append("/");
247+
}
248+
tableHtmlBuilder
249+
.append(tag.toLowerCase())
250+
.append(">");
251+
}
155252
}
156253

157254
/**
@@ -176,6 +273,12 @@ private void end(Editable output, Class kind, boolean paragraphStyle, Object...
176273
// end of the tag
177274
int len = output.length();
178275

276+
// If we're in a table, then we need to store the raw HTML for later
277+
if (tableTagLevel > 0) {
278+
final CharSequence extractedSpanText = extractSpanText(output, kind);
279+
tableHtmlBuilder.append(extractedSpanText);
280+
}
281+
179282
output.removeSpan(obj);
180283

181284
if (where != len) {
@@ -196,6 +299,21 @@ private void end(Editable output, Class kind, boolean paragraphStyle, Object...
196299
}
197300
}
198301

302+
/**
303+
* Returns the text contained within a span and deletes it from the output string
304+
*/
305+
private CharSequence extractSpanText(Editable output, Class kind) {
306+
final Object obj = getLast(output, kind);
307+
// start of the tag
308+
final int where = output.getSpanStart(obj);
309+
// end of the tag
310+
final int len = output.length();
311+
312+
final CharSequence extractedSpanText = output.subSequence(where, len);
313+
output.delete(where, len);
314+
return extractedSpanText;
315+
}
316+
199317
/**
200318
* Get last marked position of a specific tag kind (private class)
201319
*/
@@ -213,4 +331,11 @@ private static Object getLast(Editable text, Class kind) {
213331
}
214332
}
215333

334+
public void setClickableTableSpan(ClickableTableSpan clickableTableSpan) {
335+
this.mClickableTableSpan = clickableTableSpan;
336+
}
337+
338+
public void setDrawTableLinkSpan(DrawTableLinkSpan drawTableLinkSpan) {
339+
this.mDrawTableLinkSpan = drawTableLinkSpan;
340+
}
216341
}

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
}

0 commit comments

Comments
 (0)