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("\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);