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

Rudimentary table support #33

Merged
merged 1 commit into from
Feb 23, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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,94 @@
/*
* 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 = "";
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;

// This sucks, but we need this so that each table can get drawn.
// Otherwise, we end up with the default table link text (nothing) for earlier tables.
public DrawTableLinkSpan newInstance() {
final DrawTableLinkSpan drawTableLinkSpan = new DrawTableLinkSpan();
drawTableLinkSpan.setTableLinkText(mTableLinkText);
drawTableLinkSpan.setTextSize(mTextSize);
drawTableLinkSpan.setTextColor(mTextColor);

return drawTableLinkSpan;
}

@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(mTextColor);
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,55 @@ 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 = null;
if (mClickableTableSpan != null) {
clickableTableSpan = mClickableTableSpan.newInstance();
clickableTableSpan.setTableHtml(tableHtml);
}

DrawTableLinkSpan drawTableLinkSpan = null;
if (mDrawTableLinkSpan != null) {
drawTableLinkSpan = mDrawTableLinkSpan.newInstance();
}

end(output, Table.class, false, 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 +273,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 +299,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 +331,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;
}
}