Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

HTML conversion fixes and small improvements #1239

Merged
merged 4 commits into from
Oct 7, 2022
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion app/src/main/java/com/gh4a/model/Feed.java
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,12 @@ private static String generatePreview(String content) {
return null;
}
String preview = content.length() > 2000 ? content.substring(0, 2000) : content;
preview = preview.replaceAll("<(.|\n)*?>", "").trim();
preview = preview.replaceAll("<(.|\n)*?>", "")
.replace("&lt;", "<")
.replace("&gt;", ">")
.replace("&amp;", "&")
.replace("&#8217;", "’")
.trim();
if (preview.length() > 500) {
preview = preview.substring(0, 500);
}
Expand Down
111 changes: 55 additions & 56 deletions app/src/main/java/com/gh4a/utils/HtmlUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import android.graphics.Paint.Style;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import androidx.core.content.ContextCompat;
import android.text.Html.ImageGetter;
import android.text.Layout;
import android.text.Spannable;
Expand Down Expand Up @@ -63,6 +62,8 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import androidx.core.content.ContextCompat;

import static android.graphics.Paint.Style.FILL;

public class HtmlUtils {
Expand Down Expand Up @@ -129,6 +130,40 @@ public void drawBackground(Canvas c, Paint p, int left, int right, int top, int
}
}

private static class NumberedItemSpan implements LeadingMarginSpan {
private final int mNumber;
private final float mTextScaling;

public NumberedItemSpan(int number, float textScaling) {
mNumber = number;
mTextScaling = textScaling;
}

private String getItemText() {
return mNumber + ". ";
}

@Override
public int getLeadingMargin(boolean first) {
// Since we don't have access to the Paint object to measure text width here, we have to
// compute the width in a more approximate way by taking into account the current text scaling
return 4 + Math.round(getItemText().length() * 6 * mTextScaling);
}

@Override
public void drawLeadingMargin(Canvas c, Paint p, int x, int dir, int top, int baseline, int bottom,
CharSequence text, int start, int end, boolean first, Layout l) {
if (((Spanned) text).getSpanStart(this) == start) {
Paint.Style previousStyle = p.getStyle();

p.setStyle(Paint.Style.FILL);
c.drawText(getItemText(), x + dir, bottom - p.descent(), p);

p.setStyle(previousStyle);
}
}
}

private static class HorizontalLineSpan implements LineBackgroundSpan {
private final int mColor;
private final float mHeight;
Expand Down Expand Up @@ -237,6 +272,7 @@ private static class HtmlToSpannedConverter implements ContentHandler {
private static final float SMALL_TEXT_SIZE = 0.8f;

private final float mDividerHeight;
private final float mDisplayTextScaling;
private final int mBulletMargin;
private final int mReplyMargin;
private final int mReplyMarkerSize;
Expand Down Expand Up @@ -288,6 +324,7 @@ public HtmlToSpannedConverter(Context context, String source,
android.text.Html.ImageGetter imageGetter, Parser parser) {
final Resources res = context.getResources();
mDividerHeight = res.getDimension(R.dimen.divider_span_height);
mDisplayTextScaling = res.getDisplayMetrics().scaledDensity;
mBulletMargin = res.getDimensionPixelSize(R.dimen.bullet_span_margin);
mReplyMargin = res.getDimensionPixelSize(R.dimen.reply_span_margin);
mReplyMarkerSize = res.getDimensionPixelSize(R.dimen.reply_span_size);
Expand Down Expand Up @@ -394,23 +431,15 @@ private void handleStartTag(String tag, Attributes attributes) {
}
} else if (tag.equalsIgnoreCase("div")) {
startBlockElement(attributes);
String cssClass = attributes.getValue("", "class");
if (cssClass != null && cssClass.indexOf("highlight") == 0) {
start(new CodeDiv());
}
CodeDiv code = getLast(CodeDiv.class);
if (code != null) {
code.mLevel++;
}
} else if (tag.equalsIgnoreCase("span")) {
startCssStyle(attributes);
} else if (tag.equalsIgnoreCase("hr")) {
HorizontalLineSpan span = new HorizontalLineSpan(mDividerHeight, 0x60aaaaaa);
// enforce the following newlines to be written
mSpannableStringBuilder.append(' ');
appendNewlines(2);
appendNewlines(1);
mSpannableStringBuilder.append(' '); // enforce the following newline to be written
appendNewlines(1);
int len = mSpannableStringBuilder.length();
mSpannableStringBuilder.setSpan(span, len - 1, len, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
mSpannableStringBuilder.setSpan(span, len - 2, len, Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
} else if (tag.equalsIgnoreCase("strong")) {
start(new Bold());
} else if (tag.equalsIgnoreCase("b")) {
Expand All @@ -431,14 +460,10 @@ private void handleStartTag(String tag, Attributes attributes) {
startFont(attributes);
} else if (tag.equalsIgnoreCase("blockquote")) {
startBlockquote(attributes);
} else if (tag.equalsIgnoreCase("tt")) {
} else if (tag.equalsIgnoreCase("samp")) {
start(new Monospace());
} else if (tag.equalsIgnoreCase("pre")) {
start(new Pre());
CodeDiv div = getLast(CodeDiv.class);
if (div != null) {
div.mHasPre = true;
}
} else if (tag.equalsIgnoreCase("a")) {
startA(attributes);
} else if (tag.equalsIgnoreCase("u")) {
Expand All @@ -453,12 +478,13 @@ private void handleStartTag(String tag, Attributes attributes) {
start(new Super());
} else if (tag.equalsIgnoreCase("sub")) {
start(new Sub());
} else if (tag.equalsIgnoreCase("code")) {
} else if (tag.equalsIgnoreCase("code") || tag.equalsIgnoreCase("tt")) {
boolean inPre = getLast(Pre.class) != null;
if (inPre) {
appendNewlines(1);
// GitHub code blocks have <code> tags inside a <pre> tag. In these cases, we ignore
// <code> tags since the code block formatting is already applied by handling the <pre> tag.
if (!inPre) {
start(new Code());
}
start(new Code(inPre));
} else if (tag.length() == 2 &&
Character.toLowerCase(tag.charAt(0)) == 'h' &&
tag.charAt(1) >= '1' && tag.charAt(1) <= '6') {
Expand Down Expand Up @@ -493,14 +519,6 @@ private void handleEndTag(String tag) {
endLi();
} else if (tag.equalsIgnoreCase("div")) {
endBlockElement();
CodeDiv code = getLast(CodeDiv.class);
if (code != null && --code.mLevel == 0) {
if (code.mHasPre) {
setSpanFromMark(code, new CodeBlockSpan(mCodeBlockBackgroundColor));
} else {
mSpannableStringBuilder.removeSpan(code);
}
}
} else if (tag.equalsIgnoreCase("span")) {
endCssStyle();
} else if (tag.equalsIgnoreCase("strong")) {
Expand All @@ -523,10 +541,11 @@ private void handleEndTag(String tag) {
endFont();
} else if (tag.equalsIgnoreCase("blockquote")) {
endBlockquote();
} else if (tag.equalsIgnoreCase("tt")) {
} else if (tag.equalsIgnoreCase("samp")) {
end(Monospace.class, new TypefaceSpan("monospace"));
} else if (tag.equalsIgnoreCase("pre")) {
end(Pre.class, new TypefaceSpan("monospace"));
appendNewlines(1);
end(Pre.class, new TypefaceSpan("monospace"), new CodeBlockSpan(mCodeBlockBackgroundColor));
} else if (tag.equalsIgnoreCase("a")) {
endA();
} else if (tag.equalsIgnoreCase("u")) {
Expand All @@ -541,17 +560,8 @@ private void handleEndTag(String tag) {
end(Super.class, new SuperscriptSpan(), new RelativeSizeSpan(SMALL_TEXT_SIZE));
} else if (tag.equalsIgnoreCase("sub")) {
end(Sub.class, new SubscriptSpan(), new RelativeSizeSpan(SMALL_TEXT_SIZE));
} else if (tag.equalsIgnoreCase("code")) {
Code code = getLast(Code.class);
if (code != null) {
Object backgroundSpan = code.mInPre
? new CodeBlockSpan(mCodeBlockBackgroundColor)
: new BackgroundColorSpan(mCodeBlockBackgroundColor);
if (code.mInPre) {
appendNewlines(1);
}
setSpanFromMark(code, new TypefaceSpan("monospace"), backgroundSpan);
}
} else if (tag.equalsIgnoreCase("code") || tag.equalsIgnoreCase("tt")) {
end(Code.class, new TypefaceSpan("monospace"), new BackgroundColorSpan(mCodeBlockBackgroundColor));
} else if (tag.length() == 2 &&
Character.toLowerCase(tag.charAt(0)) == 'h' &&
tag.charAt(1) >= '1' && tag.charAt(1) <= '6') {
Expand Down Expand Up @@ -642,9 +652,6 @@ private void startLi(Attributes attributes) {
ListItem item = new ListItem(getLast(List.class), attributes);
startBlockElement(attributes, 1);
start(item);
if (item.mOrdered) {
mSpannableStringBuilder.insert(mSpannableStringBuilder.length(), "" + item.mPosition + ". ");
}
startCssStyle(attributes);
}

Expand All @@ -654,7 +661,7 @@ private void endLi() {
ListItem item = getLast(ListItem.class);
if (item != null) {
if (item.mOrdered) {
mSpannableStringBuilder.removeSpan(item);
setSpanFromMark(item, new NumberedItemSpan(item.mPosition, mDisplayTextScaling));
} else {
setSpanFromMark(item, new BulletSpan(mBulletMargin));
}
Expand Down Expand Up @@ -977,11 +984,6 @@ private static class Super { }
private static class Sub { }
private static class Pre { }

private static class CodeDiv {
public boolean mHasPre;
public int mLevel;
}

private static class NeedsReversingSpan {
public final Object mActualSpan;
public NeedsReversingSpan(Object actualSpan) {
Expand All @@ -990,17 +992,14 @@ public NeedsReversingSpan(Object actualSpan) {
}

private static class Code {
private final boolean mInPre;
private final int mColor;

public Code(boolean inPre) {
mInPre = inPre;
public Code() {
mColor = 0;
}

public Code(int color) {
mColor = color;
mInPre = false;
}
}

Expand Down