Compose numbered lists

This commit is contained in:
M66B 2020-08-12 10:25:08 +02:00
parent 7292d5d30f
commit 4727598373
7 changed files with 238 additions and 103 deletions

View File

@ -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("</ul>\n");
out.append(isInBulletList ? "</ul>\n" : "</ol>\n");
isInBulletList = null;
}
out.append("<br>\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 ? "</ul>\n" : "</ol>\n");
isInBulletList = null;
}
if (isBulletListItem != null && isInBulletList == null) {
// Current paragraph is the first item in a list
isInList = true;
out.append("<ul")
isInBulletList = isBulletListItem;
out.append(isInBulletList ? "<ul" : "<ol")
.append(getTextStyles(text, i, next, true, false))
.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("</ul>\n");
out.append(isInBulletList ? "</ul>\n" : "</ol>\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("</ul>\n");
if (next == end && isInBulletList != null) {
out.append(isInBulletList ? "</ul>\n" : "</ol>\n");
isInBulletList = null;
}
}

View File

@ -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;

View File

@ -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 <http://www.gnu.org/licenses/>.
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);
}
}
}

View File

@ -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;
}

View File

@ -1,20 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<group
android:id="@+id/group_style_size"
android:orderInCategory="1">
<item
android:id="@+id/menu_style_size_small"
android:title="@string/title_style_size_small" />
<item
android:id="@+id/menu_style_size"
android:orderInCategory="1"
android:title="@string/title_style_size">
<menu>
<group android:id="@+id/group_style_size">
<item
android:id="@+id/menu_style_size_small"
android:title="@string/title_style_size_small" />
<item
android:id="@+id/menu_style_size_medium"
android:title="@string/title_style_size_medium" />
<item
android:id="@+id/menu_style_size_medium"
android:title="@string/title_style_size_medium" />
<item
android:id="@+id/menu_style_size_large"
android:title="@string/title_style_size_large" />
</group>
<item
android:id="@+id/menu_style_size_large"
android:title="@string/title_style_size_large" />
</group>
</menu>
</item>
<group
android:id="@+id/group_style_color"
@ -24,17 +29,31 @@
android:title="@string/title_style_color" />
</group>
<group
android:id="@+id/group_style_list"
android:orderInCategory="3">
<item
android:id="@+id/menu_style_list"
android:title="@string/title_style_list" />
</group>
<item
android:id="@+id/menu_style_list"
android:orderInCategory="3"
android:title="@string/title_style_list">
<menu>
<group android:id="@+id/group_style_list">
<item
android:id="@+id/menu_style_list_bullets"
android:title="@string/title_style_list_bullets" />
<item
android:id="@+id/menu_style_list_numbered"
android:title="@string/title_style_list_numbered" />
</group>
</menu>
</item>
<group
android:id="@+id/group_style_font"
android:orderInCategory="4" />
<item
android:id="@+id/menu_style_font"
android:orderInCategory="4"
android:title="@string/title_style_font">
<menu>
<group android:id="@+id/group_style_font" />
</menu>
</item>
<group
android:id="@+id/group_style_clear"

View File

@ -913,10 +913,12 @@
<string name="title_style_size_small">Small</string>
<string name="title_style_size_medium">Medium</string>
<string name="title_style_size_large">Large</string>
<string name="title_style_color">Color &#8230;</string>
<string name="title_style_list">List</string>
<string name="title_style_list_bullets">Bullets</string>
<string name="title_style_list_numbered">Numbered</string>
<string name="title_style_font">Font</string>
<string name="title_style_font_default">Default</string>
<string name="title_style_color">Color &#8230;</string>
<string name="title_style_list">Create list</string>
<string name="title_style_clear">Clear formatting</string>
<string name="title_style_link">Insert link</string>

View File

@ -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("</ul>\n");
+ out.append(isInBulletList ? "</ul>\n" : "</ol>\n");
+ isInBulletList = null;
}
out.append("<br>\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 ? "</ul>\n" : "</ol>\n");
+ isInBulletList = null;
+ }
+
+ if (isBulletListItem != null && isInBulletList == null) {
// Current paragraph is the first item in a list
- isInList = true;
- out.append("<ul")
+ isInBulletList = isBulletListItem;
+ out.append(isInBulletList ? "<ul" : "<ol")
.append(getTextStyles(text, i, next, true, false))
.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("</ul>\n");
+ out.append(isInBulletList ? "</ul>\n" : "</ol>\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("</ul>\n");
+ if (next == end && isInBulletList != null) {
+ out.append(isInBulletList ? "</ul>\n" : "</ol>\n");
+ isInBulletList = null;
}
}