From e4958c0874dc3e7bec119285bb359bf98d2c5997 Mon Sep 17 00:00:00 2001
From: M66B
Date: Sun, 28 Jun 2020 20:43:20 +0200
Subject: [PATCH] to HTML with font family
---
.../eu/faircode/email/ActivityCompose.java | 2 +-
.../eu/faircode/email/ActivitySignature.java | 10 +-
app/src/main/java/eu/faircode/email/Core.java | 4 +-
.../eu/faircode/email/EditTextCompose.java | 2 +-
.../eu/faircode/email/FragmentAccount.java | 2 +-
.../eu/faircode/email/FragmentAnswer.java | 6 +-
.../eu/faircode/email/FragmentCompose.java | 23 +-
.../eu/faircode/email/FragmentIdentity.java | 2 +-
.../eu/faircode/email/FragmentQuickSetup.java | 2 +-
.../main/java/eu/faircode/email/HtmlEx.java | 479 ++++++++++++++++++
.../java/eu/faircode/email/HtmlHelper.java | 17 +-
11 files changed, 518 insertions(+), 31 deletions(-)
create mode 100644 app/src/main/java/eu/faircode/email/HtmlEx.java
diff --git a/app/src/main/java/eu/faircode/email/ActivityCompose.java b/app/src/main/java/eu/faircode/email/ActivityCompose.java
index 002b14ad02..a3f56fc4cd 100644
--- a/app/src/main/java/eu/faircode/email/ActivityCompose.java
+++ b/app/src/main/java/eu/faircode/email/ActivityCompose.java
@@ -122,7 +122,7 @@ public class ActivityCompose extends ActivityBase implements FragmentManager.OnB
CharSequence body = intent.getCharSequenceExtra(Intent.EXTRA_TEXT);
if (body != null)
if (body instanceof Spanned)
- args.putString("body", HtmlHelper.toHtml((Spanned) body));
+ args.putString("body", HtmlHelper.toHtml((Spanned) body, this));
else {
String text = body.toString();
if (!TextUtils.isEmpty(text)) {
diff --git a/app/src/main/java/eu/faircode/email/ActivitySignature.java b/app/src/main/java/eu/faircode/email/ActivitySignature.java
index 42b9b5ec0e..8c99056de9 100644
--- a/app/src/main/java/eu/faircode/email/ActivitySignature.java
+++ b/app/src/main/java/eu/faircode/email/ActivitySignature.java
@@ -194,7 +194,7 @@ public class ActivitySignature extends ActivityBase {
public Drawable getDrawable(String source) {
return ImageHelper.decodeImage(ActivitySignature.this, -1, source, true, 0, etText);
}
- }, null));
+ }, null, this));
dirty = false;
}
@@ -207,7 +207,9 @@ public class ActivitySignature extends ActivityBase {
private void save() {
etText.clearComposingText();
- String html = (raw ? etText.getText().toString() : HtmlHelper.toHtml(etText.getText()));
+ String html = (raw
+ ? etText.getText().toString()
+ : HtmlHelper.toHtml(etText.getText(), this));
Intent result = new Intent();
result.putExtra("html", html);
setResult(RESULT_OK, result);
@@ -218,7 +220,9 @@ public class ActivitySignature extends ActivityBase {
this.raw = raw;
if (!raw || dirty) {
- String html = (raw ? HtmlHelper.toHtml(etText.getText()) : etText.getText().toString());
+ String html = (raw
+ ? HtmlHelper.toHtml(etText.getText(), this)
+ : etText.getText().toString());
getIntent().putExtra("html", html);
}
diff --git a/app/src/main/java/eu/faircode/email/Core.java b/app/src/main/java/eu/faircode/email/Core.java
index a468391169..c20df856e4 100644
--- a/app/src/main/java/eu/faircode/email/Core.java
+++ b/app/src/main/java/eu/faircode/email/Core.java
@@ -3374,7 +3374,7 @@ class Core {
// Device
builder.setStyle(new NotificationCompat.BigTextStyle()
- .bigText(HtmlHelper.fromHtml(sb.toString()))
+ .bigText(HtmlHelper.fromHtml(sb.toString(), context))
.setSummaryText(title));
}
@@ -3663,7 +3663,7 @@ class Core {
if (sbm.length() > 0) {
NotificationCompat.BigTextStyle bigText = new NotificationCompat.BigTextStyle()
- .bigText(HtmlHelper.fromHtml(sbm.toString()));
+ .bigText(HtmlHelper.fromHtml(sbm.toString(), context));
if (!TextUtils.isEmpty(message.subject))
bigText.setSummaryText(message.subject);
diff --git a/app/src/main/java/eu/faircode/email/EditTextCompose.java b/app/src/main/java/eu/faircode/email/EditTextCompose.java
index b16b0783e6..407fe14e18 100644
--- a/app/src/main/java/eu/faircode/email/EditTextCompose.java
+++ b/app/src/main/java/eu/faircode/email/EditTextCompose.java
@@ -81,7 +81,7 @@ public class EditTextCompose extends FixedEditText {
}
Document document = HtmlHelper.sanitizeCompose(context, html, false);
- Spanned paste = HtmlHelper.fromHtml(document.html(), new Html.ImageGetter() {
+ Spanned paste = HtmlHelper.fromDocument(getContext(), document, new Html.ImageGetter() {
@Override
public Drawable getDrawable(String source) {
return ImageHelper.decodeImage(getContext(),
diff --git a/app/src/main/java/eu/faircode/email/FragmentAccount.java b/app/src/main/java/eu/faircode/email/FragmentAccount.java
index ff3b770ca2..fbafc5f195 100644
--- a/app/src/main/java/eu/faircode/email/FragmentAccount.java
+++ b/app/src/main/java/eu/faircode/email/FragmentAccount.java
@@ -1373,7 +1373,7 @@ public class FragmentAccount extends FragmentBase {
btnSupport.setVisibility(View.VISIBLE);
if (provider != null && provider.documentation != null) {
- tvInstructions.setText(HtmlHelper.fromHtml(provider.documentation.toString()));
+ tvInstructions.setText(HtmlHelper.fromHtml(provider.documentation.toString(), getContext()));
tvInstructions.setVisibility(View.VISIBLE);
}
diff --git a/app/src/main/java/eu/faircode/email/FragmentAnswer.java b/app/src/main/java/eu/faircode/email/FragmentAnswer.java
index 306ae74335..83bb34f0a6 100644
--- a/app/src/main/java/eu/faircode/email/FragmentAnswer.java
+++ b/app/src/main/java/eu/faircode/email/FragmentAnswer.java
@@ -171,7 +171,7 @@ public class FragmentAnswer extends FragmentBase {
public Drawable getDrawable(String source) {
return ImageHelper.decodeImage(getContext(), -1, source, true, 0, etText);
}
- }, null));
+ }, null, getContext()));
}
bottom_navigation.findViewById(R.id.action_delete).setVisibility(answer == null ? View.GONE : View.VISIBLE);
@@ -236,7 +236,7 @@ public class FragmentAnswer extends FragmentBase {
args.putString("name", etName.getText().toString().trim());
args.putBoolean("favorite", cbFavorite.isChecked());
args.putBoolean("hide", cbHide.isChecked());
- args.putString("text", HtmlHelper.toHtml(etText.getText()));
+ args.putString("text", HtmlHelper.toHtml(etText.getText(), getContext()));
new SimpleTask() {
@Override
@@ -425,7 +425,7 @@ public class FragmentAnswer extends FragmentBase {
getString(R.string.title_answer_template_name) +
"
" +
getString(R.string.title_answer_template_email) +
- "
");
+ "", getContext());
View dview = LayoutInflater.from(getContext()).inflate(R.layout.dialog_ask_again, null);
TextView tvMessage = dview.findViewById(R.id.tvMessage);
diff --git a/app/src/main/java/eu/faircode/email/FragmentCompose.java b/app/src/main/java/eu/faircode/email/FragmentCompose.java
index 6abb2d8f11..aa624add68 100644
--- a/app/src/main/java/eu/faircode/email/FragmentCompose.java
+++ b/app/src/main/java/eu/faircode/email/FragmentCompose.java
@@ -969,7 +969,7 @@ public class FragmentCompose extends FragmentBase {
Bundle args = new Bundle();
args.putLong("id", working);
args.putBoolean("plain", plain);
- args.putString("body", HtmlHelper.toHtml(etBody.getText()));
+ args.putString("body", HtmlHelper.toHtml(etBody.getText(), getContext()));
new SimpleTask() {
@Override
@@ -1029,7 +1029,7 @@ public class FragmentCompose extends FragmentBase {
private void deleteRef() {
Bundle extras = new Bundle();
- extras.putString("html", HtmlHelper.toHtml(etBody.getText()));
+ extras.putString("html", HtmlHelper.toHtml(etBody.getText(), getContext()));
extras.putBoolean("show", true);
onAction(R.id.action_save, extras, "refdelete");
}
@@ -1965,12 +1965,13 @@ public class FragmentCompose extends FragmentBase {
args.putInt("start", start);
- return HtmlHelper.fromHtml(HtmlHelper.toHtml(s), new Html.ImageGetter() {
+ // TODO: double conversion
+ return HtmlHelper.fromHtml(HtmlHelper.toHtml(s, getContext()), new Html.ImageGetter() {
@Override
public Drawable getDrawable(String source) {
return ImageHelper.decodeImage(context, id, source, true, zoom, etBody);
}
- }, null);
+ }, null, getContext());
}
@Override
@@ -2751,14 +2752,14 @@ public class FragmentCompose extends FragmentBase {
} catch (AddressException ignored) {
}
- String text = EntityAnswer.replacePlaceholders(answer, to);
+ String html = EntityAnswer.replacePlaceholders(answer, to);
- Spanned spanned = HtmlHelper.fromHtml(text, new Html.ImageGetter() {
+ Spanned spanned = HtmlHelper.fromHtml(html, new Html.ImageGetter() {
@Override
public Drawable getDrawable(String source) {
return ImageHelper.decodeImage(getContext(), working, source, true, zoom, etBody);
}
- }, null);
+ }, null, getContext());
etBody.getText().insert(etBody.getSelectionStart(), spanned);
}
@@ -2802,7 +2803,7 @@ public class FragmentCompose extends FragmentBase {
if (!etSubject.getText().toString().equals(subject))
return false;
- if (!TextUtils.isEmpty(JsoupEx.parse(HtmlHelper.toHtml(etBody.getText())).text().trim()))
+ if (!TextUtils.isEmpty(JsoupEx.parse(HtmlHelper.toHtml(etBody.getText(), getContext())).text().trim()))
return false;
if (rvAttachment.getAdapter().getItemCount() > 0)
@@ -2831,7 +2832,7 @@ public class FragmentCompose extends FragmentBase {
args.putString("cc", etCc.getText().toString().trim());
args.putString("bcc", etBcc.getText().toString().trim());
args.putString("subject", etSubject.getText().toString().trim());
- args.putString("body", HtmlHelper.toHtml(etBody.getText()));
+ args.putString("body", HtmlHelper.toHtml(etBody.getText(), getContext()));
args.putBoolean("signature", cbSignature.isChecked());
args.putBoolean("empty", isEmpty());
args.putBoolean("interactive", getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED));
@@ -4629,7 +4630,7 @@ public class FragmentCompose extends FragmentBase {
Elements ref = doc.select("div[fairemail=reference]");
ref.remove();
- Spanned spannedBody = HtmlHelper.fromHtml(doc.html(), new Html.ImageGetter() {
+ Spanned spannedBody = HtmlHelper.fromDocument(context, doc, new Html.ImageGetter() {
@Override
public Drawable getDrawable(String source) {
return ImageHelper.decodeImage(context, id, source, true, zoom, etBody);
@@ -4759,7 +4760,7 @@ public class FragmentCompose extends FragmentBase {
public Drawable getDrawable(String source) {
return ImageHelper.decodeImage(getContext(), working, source, true, 0, tvSignature);
}
- }, null);
+ }, null, getContext());
tvSignature.setText(signature);
grpSignature.setVisibility(signature == null ? View.GONE : View.VISIBLE);
diff --git a/app/src/main/java/eu/faircode/email/FragmentIdentity.java b/app/src/main/java/eu/faircode/email/FragmentIdentity.java
index 06e0e65ab9..2cdb6cd999 100644
--- a/app/src/main/java/eu/faircode/email/FragmentIdentity.java
+++ b/app/src/main/java/eu/faircode/email/FragmentIdentity.java
@@ -995,7 +995,7 @@ public class FragmentIdentity extends FragmentBase {
btnSupport.setVisibility(View.VISIBLE);
if (provider != null && provider.documentation != null) {
- tvInstructions.setText(HtmlHelper.fromHtml(provider.documentation.toString()));
+ tvInstructions.setText(HtmlHelper.fromHtml(provider.documentation.toString(), getContext()));
tvInstructions.setVisibility(View.VISIBLE);
}
diff --git a/app/src/main/java/eu/faircode/email/FragmentQuickSetup.java b/app/src/main/java/eu/faircode/email/FragmentQuickSetup.java
index e85b945407..7e8986152a 100644
--- a/app/src/main/java/eu/faircode/email/FragmentQuickSetup.java
+++ b/app/src/main/java/eu/faircode/email/FragmentQuickSetup.java
@@ -491,7 +491,7 @@ public class FragmentQuickSetup extends FragmentBase {
btnSupport.setVisibility(View.VISIBLE);
if (args.containsKey("documentation")) {
- tvInstructions.setText(HtmlHelper.fromHtml(args.getString("documentation")));
+ tvInstructions.setText(HtmlHelper.fromHtml(args.getString("documentation"), getContext()));
tvInstructions.setVisibility(View.VISIBLE);
}
diff --git a/app/src/main/java/eu/faircode/email/HtmlEx.java b/app/src/main/java/eu/faircode/email/HtmlEx.java
new file mode 100644
index 0000000000..c8b8e1b8f5
--- /dev/null
+++ b/app/src/main/java/eu/faircode/email/HtmlEx.java
@@ -0,0 +1,479 @@
+package eu.faircode.email;
+
+/*
+ * Copyright (C) 2007 The Android Open Source Project
+ *
+ * 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.
+ */
+
+import android.content.Context;
+import android.graphics.Typeface;
+import android.text.Layout;
+import android.text.Spanned;
+import android.text.TextDirectionHeuristics;
+import android.text.TextUtils;
+import android.text.style.AbsoluteSizeSpan;
+import android.text.style.AlignmentSpan;
+import android.text.style.BackgroundColorSpan;
+import android.text.style.BulletSpan;
+import android.text.style.CharacterStyle;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.ImageSpan;
+import android.text.style.ParagraphStyle;
+import android.text.style.QuoteSpan;
+import android.text.style.RelativeSizeSpan;
+import android.text.style.StrikethroughSpan;
+import android.text.style.StyleSpan;
+import android.text.style.SubscriptSpan;
+import android.text.style.SuperscriptSpan;
+import android.text.style.TypefaceSpan;
+import android.text.style.URLSpan;
+import android.text.style.UnderlineSpan;
+
+import static android.text.Html.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE;
+
+public class HtmlEx {
+ private Context context;
+
+ private static final int TO_HTML_PARAGRAPH_FLAG = 0x00000001;
+
+ public HtmlEx(Context context){
+ this.context = context;
+ }
+
+ /**
+ * @deprecated use {@link #toHtml(Spanned, int)} instead.
+ */
+ @Deprecated
+ public /* static */ String toHtml(Spanned text) {
+ return toHtml(text, TO_HTML_PARAGRAPH_LINES_CONSECUTIVE);
+ }
+
+ /**
+ * Returns an HTML representation of the provided Spanned text. A best effort is
+ * made to add HTML tags corresponding to spans. Also note that HTML metacharacters
+ * (such as "<" and "&") within the input text are escaped.
+ *
+ * @param text input text to convert
+ * @param option one of {@link #TO_HTML_PARAGRAPH_LINES_CONSECUTIVE} or
+ * {@link #TO_HTML_PARAGRAPH_LINES_INDIVIDUAL}
+ * @return string containing input converted to HTML
+ */
+ public /* static */ String toHtml(Spanned text, int option) {
+ StringBuilder out = new StringBuilder();
+ withinHtml(out, text, option);
+ return out.toString();
+ }
+
+ /**
+ * Returns an HTML escaped representation of the given plain text.
+ */
+ public /* static */ String escapeHtml(CharSequence text) {
+ StringBuilder out = new StringBuilder();
+ withinStyle(out, text, 0, text.length());
+ return out.toString();
+ }
+
+ private /* static */ void withinHtml(StringBuilder out, Spanned text, int option) {
+ if ((option & TO_HTML_PARAGRAPH_FLAG) == TO_HTML_PARAGRAPH_LINES_CONSECUTIVE) {
+ encodeTextAlignmentByDiv(out, text, option);
+ return;
+ }
+
+ withinDiv(out, text, 0, text.length(), option);
+ }
+
+ private /* static */ void encodeTextAlignmentByDiv(StringBuilder out, Spanned text, int option) {
+ int len = text.length();
+
+ int next;
+ for (int i = 0; i < len; i = next) {
+ next = text.nextSpanTransition(i, len, ParagraphStyle.class);
+ ParagraphStyle[] style = text.getSpans(i, next, ParagraphStyle.class);
+ String elements = " ";
+ boolean needDiv = false;
+
+ for(int j = 0; j < style.length; j++) {
+ if (style[j] instanceof AlignmentSpan) {
+ Layout.Alignment align =
+ ((AlignmentSpan) style[j]).getAlignment();
+ needDiv = true;
+ if (align == Layout.Alignment.ALIGN_CENTER) {
+ elements = "align=\"center\" " + elements;
+ } else if (align == Layout.Alignment.ALIGN_OPPOSITE) {
+ elements = "align=\"right\" " + elements;
+ } else {
+ elements = "align=\"left\" " + elements;
+ }
+ }
+ }
+ if (needDiv) {
+ out.append("");
+ }
+
+ withinDiv(out, text, i, next, option);
+
+ if (needDiv) {
+ out.append("
");
+ }
+ }
+ }
+
+ private /* static */ void withinDiv(StringBuilder out, Spanned text, int start, int end,
+ int option) {
+ int next;
+ for (int i = start; i < end; i = next) {
+ next = text.nextSpanTransition(i, end, QuoteSpan.class);
+ QuoteSpan[] quotes = text.getSpans(i, next, QuoteSpan.class);
+
+ for (QuoteSpan quote : quotes) {
+ out.append("");
+ }
+
+ withinBlockquote(out, text, i, next, option);
+
+ for (QuoteSpan quote : quotes) {
+ out.append("
\n");
+ }
+ }
+ }
+
+ private /* static */ String getTextDirection(Spanned text, int start, int end) {
+ if (TextDirectionHeuristics.FIRSTSTRONG_LTR.isRtl(text, start, end - start)) {
+ return " dir=\"rtl\"";
+ } else {
+ return " dir=\"ltr\"";
+ }
+ }
+
+ private /* static */ String getTextStyles(Spanned text, int start, int end,
+ boolean forceNoVerticalMargin, boolean includeTextAlign) {
+ String margin = null;
+ String textAlign = null;
+
+ if (forceNoVerticalMargin) {
+ margin = "margin-top:0; margin-bottom:0;";
+ }
+ if (includeTextAlign) {
+ final AlignmentSpan[] alignmentSpans = text.getSpans(start, end, AlignmentSpan.class);
+
+ // Only use the last AlignmentSpan with flag SPAN_PARAGRAPH
+ for (int i = alignmentSpans.length - 1; i >= 0; i--) {
+ AlignmentSpan s = alignmentSpans[i];
+ if ((text.getSpanFlags(s) & Spanned.SPAN_PARAGRAPH) == Spanned.SPAN_PARAGRAPH) {
+ final Layout.Alignment alignment = s.getAlignment();
+ if (alignment == Layout.Alignment.ALIGN_NORMAL) {
+ textAlign = "text-align:start;";
+ } else if (alignment == Layout.Alignment.ALIGN_CENTER) {
+ textAlign = "text-align:center;";
+ } else if (alignment == Layout.Alignment.ALIGN_OPPOSITE) {
+ textAlign = "text-align:end;";
+ }
+ break;
+ }
+ }
+ }
+
+ if (margin == null && textAlign == null) {
+ return "";
+ }
+
+ final StringBuilder style = new StringBuilder(" style=\"");
+ if (margin != null && textAlign != null) {
+ style.append(margin).append(" ").append(textAlign);
+ } else if (margin != null) {
+ style.append(margin);
+ } else if (textAlign != null) {
+ style.append(textAlign);
+ }
+
+ return style.append("\"").toString();
+ }
+
+ private /* static */ void withinBlockquote(StringBuilder out, Spanned text, int start, int end,
+ int option) {
+ if ((option & TO_HTML_PARAGRAPH_FLAG) == TO_HTML_PARAGRAPH_LINES_CONSECUTIVE) {
+ withinBlockquoteConsecutive(out, text, start, end);
+ } else {
+ withinBlockquoteIndividual(out, text, start, end);
+ }
+ }
+
+ private /* static */ void withinBlockquoteIndividual(StringBuilder out, Spanned text, int start,
+ int end) {
+ boolean isInList = false;
+ int next;
+ for (int i = start; i <= end; i = next) {
+ next = TextUtils.indexOf(text, '\n', i, end);
+ if (next < 0) {
+ next = end;
+ }
+
+ if (next == i) {
+ if (isInList) {
+ // Current paragraph is no longer a list item; close the previously opened list
+ isInList = false;
+ out.append("\n");
+ }
+ out.append("
\n");
+ } else {
+ boolean isListItem = false;
+ ParagraphStyle[] paragraphStyles = text.getSpans(i, next, ParagraphStyle.class);
+ for (ParagraphStyle paragraphStyle : paragraphStyles) {
+ final int spanFlags = text.getSpanFlags(paragraphStyle);
+ if ((spanFlags & Spanned.SPAN_PARAGRAPH) == Spanned.SPAN_PARAGRAPH
+ && paragraphStyle instanceof BulletSpan) {
+ isListItem = true;
+ break;
+ }
+ }
+
+ if (isListItem && !isInList) {
+ // Current paragraph is the first item in a list
+ isInList = true;
+ out.append("\n");
+ }
+
+ if (isInList && !isListItem) {
+ // Current paragraph is no longer a list item; close the previously opened list
+ isInList = false;
+ out.append("
\n");
+ }
+
+ String tagType = isListItem ? "li" : "p";
+ out.append("<").append(tagType)
+ .append(getTextDirection(text, i, next))
+ .append(getTextStyles(text, i, next, !isListItem, true))
+ .append(">");
+
+ withinParagraph(out, text, i, next);
+
+ out.append("");
+ out.append(tagType);
+ out.append(">\n");
+
+ if (next == end && isInList) {
+ isInList = false;
+ out.append("\n");
+ }
+ }
+
+ next++;
+ }
+ }
+
+ private /* static */ void withinBlockquoteConsecutive(StringBuilder out, Spanned text, int start,
+ int end) {
+ out.append("");
+
+ int next;
+ for (int i = start; i < end; i = next) {
+ next = TextUtils.indexOf(text, '\n', i, end);
+ if (next < 0) {
+ next = end;
+ }
+
+ int nl = 0;
+
+ while (next < end && text.charAt(next) == '\n') {
+ nl++;
+ next++;
+ }
+
+ withinParagraph(out, text, i, next - nl);
+
+ if (nl == 1) {
+ out.append("
\n");
+ } else {
+ for (int j = 2; j < nl; j++) {
+ out.append("
");
+ }
+ if (next != end) {
+ /* Paragraph should be closed and reopened */
+ out.append("
\n");
+ out.append("");
+ }
+ }
+ }
+
+ out.append("
\n");
+ }
+
+ private /* static */ void withinParagraph(StringBuilder out, Spanned text, int start, int end) {
+ int next;
+ for (int i = start; i < end; i = next) {
+ next = text.nextSpanTransition(i, end, CharacterStyle.class);
+ CharacterStyle[] style = text.getSpans(i, next, CharacterStyle.class);
+
+ for (int j = 0; j < style.length; j++) {
+ if (style[j] instanceof StyleSpan) {
+ int s = ((StyleSpan) style[j]).getStyle();
+
+ if ((s & Typeface.BOLD) != 0) {
+ out.append("");
+ }
+ if ((s & Typeface.ITALIC) != 0) {
+ out.append("");
+ }
+ }
+ if (style[j] instanceof TypefaceSpan) {
+ String s = ((TypefaceSpan) style[j]).getFamily();
+
+ //if ("monospace".equals(s)) {
+ // out.append("");
+ //}
+
+ out.append("");
+ }
+ if (style[j] instanceof SuperscriptSpan) {
+ out.append("");
+ }
+ if (style[j] instanceof SubscriptSpan) {
+ out.append("");
+ }
+ if (style[j] instanceof UnderlineSpan) {
+ out.append("");
+ }
+ if (style[j] instanceof StrikethroughSpan) {
+ out.append("");
+ }
+ if (style[j] instanceof URLSpan) {
+ out.append("");
+ }
+ if (style[j] instanceof ImageSpan) {
+ out.append("");
+
+ // Don't output the dummy character underlying the image.
+ i = next;
+ }
+ if (style[j] instanceof AbsoluteSizeSpan) {
+ AbsoluteSizeSpan s = ((AbsoluteSizeSpan) style[j]);
+ float sizeDip = s.getSize();
+ if (!s.getDip()) {
+ //Application application = ActivityThread.currentApplication();
+ sizeDip /= context.getResources().getDisplayMetrics().density;
+ }
+
+ // px in CSS is the equivalance of dip in Android
+ out.append(String.format("", sizeDip));
+ }
+ if (style[j] instanceof RelativeSizeSpan) {
+ float sizeEm = ((RelativeSizeSpan) style[j]).getSizeChange();
+ out.append(String.format("", sizeEm));
+ }
+ if (style[j] instanceof ForegroundColorSpan) {
+ int color = ((ForegroundColorSpan) style[j]).getForegroundColor();
+ out.append(String.format("", 0xFFFFFF & color));
+ }
+ if (style[j] instanceof BackgroundColorSpan) {
+ int color = ((BackgroundColorSpan) style[j]).getBackgroundColor();
+ out.append(String.format("",
+ 0xFFFFFF & color));
+ }
+ }
+
+ withinStyle(out, text, i, next);
+
+ for (int j = style.length - 1; j >= 0; j--) {
+ if (style[j] instanceof BackgroundColorSpan) {
+ out.append("");
+ }
+ if (style[j] instanceof ForegroundColorSpan) {
+ out.append("");
+ }
+ if (style[j] instanceof RelativeSizeSpan) {
+ out.append("");
+ }
+ if (style[j] instanceof AbsoluteSizeSpan) {
+ out.append("");
+ }
+ if (style[j] instanceof URLSpan) {
+ out.append("");
+ }
+ if (style[j] instanceof StrikethroughSpan) {
+ out.append("");
+ }
+ if (style[j] instanceof UnderlineSpan) {
+ out.append("");
+ }
+ if (style[j] instanceof SubscriptSpan) {
+ out.append("");
+ }
+ if (style[j] instanceof SuperscriptSpan) {
+ out.append("");
+ }
+ if (style[j] instanceof TypefaceSpan) {
+ //String s = ((TypefaceSpan) style[j]).getFamily();
+
+ //if (s.equals("monospace")) {
+ // out.append("");
+ //}
+
+ out.append("");
+ }
+ if (style[j] instanceof StyleSpan) {
+ int s = ((StyleSpan) style[j]).getStyle();
+
+ if ((s & Typeface.BOLD) != 0) {
+ out.append("");
+ }
+ if ((s & Typeface.ITALIC) != 0) {
+ out.append("");
+ }
+ }
+ }
+ }
+ }
+
+ //@UnsupportedAppUsage
+ private /* static */ void withinStyle(StringBuilder out, CharSequence text,
+ int start, int end) {
+ for (int i = start; i < end; i++) {
+ char c = text.charAt(i);
+
+ if (c == '<') {
+ out.append("<");
+ } else if (c == '>') {
+ out.append(">");
+ } else if (c == '&') {
+ out.append("&");
+ } else if (c >= 0xD800 && c <= 0xDFFF) {
+ if (c < 0xDC00 && i + 1 < end) {
+ char d = text.charAt(i + 1);
+ if (d >= 0xDC00 && d <= 0xDFFF) {
+ i++;
+ int codepoint = 0x010000 | (int) c - 0xD800 << 10 | (int) d - 0xDC00;
+ out.append("").append(codepoint).append(";");
+ }
+ }
+ } else if (c > 0x7E || c < ' ') {
+ out.append("").append((int) c).append(";");
+ } else if (c == ' ') {
+ while (i + 1 < end && text.charAt(i + 1) == ' ') {
+ out.append(" ");
+ i++;
+ }
+
+ out.append(' ');
+ } else {
+ out.append(c);
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/eu/faircode/email/HtmlHelper.java b/app/src/main/java/eu/faircode/email/HtmlHelper.java
index 5e07707903..9bac676b93 100644
--- a/app/src/main/java/eu/faircode/email/HtmlHelper.java
+++ b/app/src/main/java/eu/faircode/email/HtmlHelper.java
@@ -62,7 +62,6 @@ import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import androidx.core.graphics.ColorUtils;
-import androidx.core.text.HtmlCompat;
import androidx.core.util.PatternsCompat;
import androidx.preference.PreferenceManager;
@@ -112,7 +111,6 @@ import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
-import static androidx.core.text.HtmlCompat.FROM_HTML_SEPARATOR_LINE_BREAK_LIST_ITEM;
import static androidx.core.text.HtmlCompat.TO_HTML_PARAGRAPH_LINES_CONSECUTIVE;
import static org.w3c.css.sac.Condition.SAC_CLASS_CONDITION;
@@ -2221,11 +2219,14 @@ public class HtmlHelper {
return ssb;
}
- static Spanned fromHtml(@NonNull String html) {
- return fromHtml(html, null, null);
+ static Spanned fromHtml(@NonNull String html, Context context) {
+ return fromHtml(html, null, null, context);
}
- static Spanned fromHtml(@NonNull String html, @Nullable Html.ImageGetter imageGetter, @Nullable Html.TagHandler tagHandler) {
+ static Spanned fromHtml(@NonNull String html, @Nullable Html.ImageGetter imageGetter, @Nullable Html.TagHandler tagHandler, Context context) {
+ Document document = JsoupEx.parse(html);
+ return fromDocument(context, document, false, imageGetter, tagHandler);
+/*
Spanned spanned = HtmlCompat.fromHtml(html, FROM_HTML_SEPARATOR_LINE_BREAK_LIST_ITEM, imageGetter, tagHandler);
int i = spanned.length();
@@ -2235,10 +2236,12 @@ public class HtmlHelper {
spanned = (Spanned) spanned.subSequence(0, i);
return reverseSpans(spanned);
+*/
}
- static String toHtml(Spanned spanned) {
- String html = HtmlCompat.toHtml(spanned, TO_HTML_PARAGRAPH_LINES_CONSECUTIVE);
+ static String toHtml(Spanned spanned, Context context) {
+ HtmlEx converter = new HtmlEx(context);
+ String html = converter.toHtml(spanned, TO_HTML_PARAGRAPH_LINES_CONSECUTIVE);
// @Google: why convert size to and from in a different way?
Document doc = JsoupEx.parse(html);