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
" +
getString(R.string.title_answer_template_email) +
- "
"); + } + + 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("
");
+
+ 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("
"); + } + } + } + + 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);