From 4727598373bffba34651cf6813b7ececc7579417 Mon Sep 17 00:00:00 2001 From: M66B Date: Wed, 12 Aug 2020 10:25:08 +0200 Subject: [PATCH] Compose numbered lists --- .../main/java/eu/faircode/email/HtmlEx.java | 39 +++++---- .../java/eu/faircode/email/HtmlHelper.java | 54 +----------- .../java/eu/faircode/email/NumberSpan.java | 64 ++++++++++++++ .../java/eu/faircode/email/StyleHelper.java | 26 ++++-- app/src/main/res/menu/popup_style.xml | 65 +++++++++----- app/src/main/res/values/strings.xml | 6 +- patches/HtmlExNumber.patch | 87 +++++++++++++++++++ 7 files changed, 238 insertions(+), 103 deletions(-) create mode 100644 app/src/main/java/eu/faircode/email/NumberSpan.java create mode 100644 patches/HtmlExNumber.patch diff --git a/app/src/main/java/eu/faircode/email/HtmlEx.java b/app/src/main/java/eu/faircode/email/HtmlEx.java index 5cfceaccd2..f6db9f051c 100644 --- a/app/src/main/java/eu/faircode/email/HtmlEx.java +++ b/app/src/main/java/eu/faircode/email/HtmlEx.java @@ -211,7 +211,7 @@ public class HtmlEx { private /* static */ void withinBlockquoteIndividual(StringBuilder out, Spanned text, int start, int end) { - boolean isInList = false; + Boolean isInBulletList = null; int next; for (int i = start; i <= end; i = next) { next = TextUtils.indexOf(text, '\n', i, end); @@ -220,42 +220,47 @@ public class HtmlEx { } if (next == i) { - if (isInList) { + if (isInBulletList != null) { // Current paragraph is no longer a list item; close the previously opened list - isInList = false; - out.append("\n"); + out.append(isInBulletList ? "\n" : "\n"); + isInBulletList = null; } out.append("
\n"); } else { - boolean isListItem = false; + Boolean isBulletListItem = null; 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; + isBulletListItem = !(paragraphStyle instanceof eu.faircode.email.NumberSpan); break; } } - if (isListItem && !isInList) { + if (isBulletListItem != null && isInBulletList != null && isBulletListItem != isInBulletList) { + out.append(isInBulletList ? "\n" : "\n"); + isInBulletList = null; + } + + if (isBulletListItem != null && isInBulletList == null) { // Current paragraph is the first item in a list - isInList = true; - out.append("\n"); } - if (isInList && !isListItem) { + if (isInBulletList != null && isBulletListItem == null) { // Current paragraph is no longer a list item; close the previously opened list - isInList = false; - out.append("\n"); + out.append(isInBulletList ? "\n" : "\n"); + isInBulletList = null; } - String tagType = isListItem ? "li" : "p"; + String tagType = isBulletListItem != null ? "li" : "p"; out.append("<").append(tagType) .append(getTextDirection(text, i, next)) - .append(getTextStyles(text, i, next, !isListItem, true)) + .append(getTextStyles(text, i, next, isBulletListItem == null, true)) .append(">"); withinParagraph(out, text, i, next); @@ -264,9 +269,9 @@ public class HtmlEx { out.append(tagType); out.append(">\n"); - if (next == end && isInList) { - isInList = false; - out.append("\n"); + if (next == end && isInBulletList != null) { + out.append(isInBulletList ? "\n" : "\n"); + isInBulletList = null; } } diff --git a/app/src/main/java/eu/faircode/email/HtmlHelper.java b/app/src/main/java/eu/faircode/email/HtmlHelper.java index 5a1cce454d..97260e93ef 100644 --- a/app/src/main/java/eu/faircode/email/HtmlHelper.java +++ b/app/src/main/java/eu/faircode/email/HtmlHelper.java @@ -767,22 +767,6 @@ public class HtmlHelper { for (Element subp : document.select("sub,sup")) subp.tagName("small"); - // Lists - // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/li - if (!view) { - for (Element li : document.select("li")) { - Element parent = li.parent(); - if (parent == null || "ul".equals(parent.tagName())) - continue; // li.prependText("• "); - else - li.prependText((li.elementSiblingIndex() + 1) + ". "); - li.tagName("span"); - li.appendElement("br"); // line break after list item - } - document.select("ol").tagName("div"); - //document.select("ul").tagName("div"); - } - // Tables // https://developer.mozilla.org/en-US/docs/Web/HTML/Element/table for (Element col : document.select("th,td")) { @@ -2291,7 +2275,7 @@ public class HtmlHelper { int s = start.get(spans[i]); int e = end.get(spans[i]); int f = flags.get(spans[i]); - if (spans[i] instanceof BulletSpan) + if (spans[i] instanceof BulletSpan || spans[i] instanceof NumberSpan) if (s > 1 && ssb.charAt(s - 1) == '\n' && e > 1 && ssb.charAt(e - 1) == '\n') f |= Spanned.SPAN_PARAGRAPH; @@ -2349,42 +2333,6 @@ public class HtmlHelper { return reverse; } - private static class NumberSpan implements LeadingMarginSpan { - private TextPaint tp; - private String number; - private int margin; - - public NumberSpan(int gapWidth, int color, float textSize, int index) { - tp = new TextPaint(); - tp.setStyle(Paint.Style.FILL); - tp.setColor(color); - tp.setTypeface(Typeface.MONOSPACE); - tp.setTextSize(textSize); - - number = index + "."; - margin = Math.round(tp.measureText(number) + gapWidth); - } - - @Override - public int getLeadingMargin(boolean first) { - // https://issuetracker.google.com/issues/36956124 - // This is called before drawLeadingMargin to justify the text - return margin; - } - - @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 layout) { - if (text instanceof Spanned && - ((Spanned) text).getSpanStart(this) == start) { - float textSize = tp.getTextSize(); - if (textSize > p.getTextSize()) - tp.setTextSize(p.getTextSize()); - c.drawText(number, x + dir, baseline, tp); - tp.setTextSize(textSize); - } - } - } - public static class LineSpan extends ReplacementSpan { private int lineColor; private float strokeWidth; diff --git a/app/src/main/java/eu/faircode/email/NumberSpan.java b/app/src/main/java/eu/faircode/email/NumberSpan.java new file mode 100644 index 0000000000..06797f355b --- /dev/null +++ b/app/src/main/java/eu/faircode/email/NumberSpan.java @@ -0,0 +1,64 @@ +package eu.faircode.email; + +/* + This file is part of FairEmail. + + FairEmail is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + FairEmail is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with FairEmail. If not, see . + + Copyright 2018-2020 by Marcel Bokhorst (M66B) +*/ + +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Typeface; +import android.text.Layout; +import android.text.Spanned; +import android.text.TextPaint; +import android.text.style.BulletSpan; + +public class NumberSpan extends BulletSpan { + private TextPaint tp; + private String number; + private int margin; + + public NumberSpan(int gapWidth, int color, float textSize, int index) { + tp = new TextPaint(); + tp.setStyle(Paint.Style.FILL); + tp.setColor(color); + tp.setTypeface(Typeface.MONOSPACE); + tp.setTextSize(textSize); + + number = index + "."; + margin = Math.round(tp.measureText(number) + gapWidth); + } + + @Override + public int getLeadingMargin(boolean first) { + // https://issuetracker.google.com/issues/36956124 + // This is called before drawLeadingMargin to justify the text + return margin; + } + + @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 layout) { + if (text instanceof Spanned && + ((Spanned) text).getSpanStart(this) == start) { + float textSize = tp.getTextSize(); + if (textSize > p.getTextSize()) + tp.setTextSize(p.getTextSize()); + c.drawText(number, x + dir, baseline, tp); + tp.setTextSize(textSize); + } + } +} diff --git a/app/src/main/java/eu/faircode/email/StyleHelper.java b/app/src/main/java/eu/faircode/email/StyleHelper.java index e5798d94a6..4f87b389e4 100644 --- a/app/src/main/java/eu/faircode/email/StyleHelper.java +++ b/app/src/main/java/eu/faircode/email/StyleHelper.java @@ -2,6 +2,7 @@ package eu.faircode.email; import android.app.Activity; import android.app.Dialog; +import android.content.Context; import android.content.DialogInterface; import android.graphics.Typeface; import android.os.Build; @@ -16,6 +17,7 @@ import android.text.style.TypefaceSpan; import android.text.style.URLSpan; import android.text.style.UnderlineSpan; import android.view.MenuItem; +import android.view.SubMenu; import android.view.View; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; @@ -96,9 +98,10 @@ public class StyleHelper { popupMenu.inflate(R.menu.popup_style); String[] fontNames = anchor.getResources().getStringArray(R.array.fontNameNames); + SubMenu smenu = popupMenu.getMenu().findItem(R.id.menu_style_font).getSubMenu(); for (int i = 0; i < fontNames.length; i++) - popupMenu.getMenu().add(R.id.group_style_font, i, 4, fontNames[i]); - popupMenu.getMenu().add(R.id.group_style_font, fontNames.length, 4, R.string.title_style_font_default); + smenu.add(R.id.group_style_font, i, 0, fontNames[i]); + smenu.add(R.id.group_style_font, fontNames.length, 0, R.string.title_style_font_default); popupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() { @Override @@ -198,18 +201,25 @@ public class StyleHelper { for (BulletSpan span : spans) t.removeSpan(span); - int colorAccent = Helper.resolveColor(etBody.getContext(), R.attr.colorAccent); - int dp3 = Helper.dp2pixels(etBody.getContext(), 3); - int dp6 = Helper.dp2pixels(etBody.getContext(), 6); + Context context = etBody.getContext(); + int colorAccent = Helper.resolveColor(context, R.attr.colorAccent); + int dp3 = Helper.dp2pixels(context, 3); + int dp6 = Helper.dp2pixels(context, 6); + float textSize = Helper.getTextSize(context, 0); + int i = s; int j = s + 1; + int index = 1; while (j < e) { if (i > 0 && t.charAt(i - 1) == '\n' && t.charAt(j) == '\n') { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) - t.setSpan(new BulletSpan(dp6, colorAccent), i, j + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_PARAGRAPH); + if (item.getItemId() == R.id.menu_style_list_bullets) + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.P) + t.setSpan(new BulletSpan(dp6, colorAccent), i, j + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_PARAGRAPH); + else + t.setSpan(new BulletSpan(dp6, colorAccent, dp3), i, j + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_PARAGRAPH); else - t.setSpan(new BulletSpan(dp6, colorAccent, dp3), i, j + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_PARAGRAPH); + t.setSpan(new NumberSpan(dp6, colorAccent, textSize, index++), i, j + 1, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_PARAGRAPH); i = j + 1; } diff --git a/app/src/main/res/menu/popup_style.xml b/app/src/main/res/menu/popup_style.xml index 2481eb05dc..25365b6b76 100644 --- a/app/src/main/res/menu/popup_style.xml +++ b/app/src/main/res/menu/popup_style.xml @@ -1,20 +1,25 @@ - - + + + + - + - - + + + + - - - + + + + + + + + - + + + + + + Small Medium Large + Color … + List + Bullets + Numbered Font Default - Color … - Create list Clear formatting Insert link diff --git a/patches/HtmlExNumber.patch b/patches/HtmlExNumber.patch new file mode 100644 index 0000000000..db7c66386c --- /dev/null +++ b/patches/HtmlExNumber.patch @@ -0,0 +1,87 @@ +diff --git a/app/src/main/java/eu/faircode/email/HtmlEx.java b/app/src/main/java/eu/faircode/email/HtmlEx.java +index 5cfceaccd..f6db9f051 100644 +--- a/app/src/main/java/eu/faircode/email/HtmlEx.java ++++ b/app/src/main/java/eu/faircode/email/HtmlEx.java +@@ -211,7 +211,7 @@ public class HtmlEx { + + private /* static */ void withinBlockquoteIndividual(StringBuilder out, Spanned text, int start, + int end) { +- boolean isInList = false; ++ Boolean isInBulletList = null; + int next; + for (int i = start; i <= end; i = next) { + next = TextUtils.indexOf(text, '\n', i, end); +@@ -220,42 +220,47 @@ public class HtmlEx { + } + + if (next == i) { +- if (isInList) { ++ if (isInBulletList != null) { + // Current paragraph is no longer a list item; close the previously opened list +- isInList = false; +- out.append("\n"); ++ out.append(isInBulletList ? "\n" : "\n"); ++ isInBulletList = null; + } + out.append("
\n"); + } else { +- boolean isListItem = false; ++ Boolean isBulletListItem = null; + 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; ++ isBulletListItem = !(paragraphStyle instanceof eu.faircode.email.NumberSpan); + break; + } + } + +- if (isListItem && !isInList) { ++ if (isBulletListItem != null && isInBulletList != null && isBulletListItem != isInBulletList) { ++ out.append(isInBulletList ? "\n" : "\n"); ++ isInBulletList = null; ++ } ++ ++ if (isBulletListItem != null && isInBulletList == null) { + // Current paragraph is the first item in a list +- isInList = true; +- out.append("\n"); + } + +- if (isInList && !isListItem) { ++ if (isInBulletList != null && isBulletListItem == null) { + // Current paragraph is no longer a list item; close the previously opened list +- isInList = false; +- out.append("\n"); ++ out.append(isInBulletList ? "\n" : "\n"); ++ isInBulletList = null; + } + +- String tagType = isListItem ? "li" : "p"; ++ String tagType = isBulletListItem != null ? "li" : "p"; + out.append("<").append(tagType) + .append(getTextDirection(text, i, next)) +- .append(getTextStyles(text, i, next, !isListItem, true)) ++ .append(getTextStyles(text, i, next, isBulletListItem == null, true)) + .append(">"); + + withinParagraph(out, text, i, next); +@@ -264,9 +269,9 @@ public class HtmlEx { + out.append(tagType); + out.append(">\n"); + +- if (next == end && isInList) { +- isInList = false; +- out.append("\n"); ++ if (next == end && isInBulletList != null) { ++ out.append(isInBulletList ? "\n" : "\n"); ++ isInBulletList = null; + } + } +