Add AbsoluteSizeSpan to SpannedHtmlConverter

PiperOrigin-RevId: 309390205
This commit is contained in:
ibaker 2020-05-01 12:44:04 +01:00 committed by Oliver Woodman
parent fa0178d043
commit eb7c14704f
3 changed files with 90 additions and 25 deletions

View file

@ -19,6 +19,7 @@ package com.google.android.exoplayer2.ui;
import android.graphics.Typeface;
import android.text.Html;
import android.text.Spanned;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.BackgroundColorSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
@ -66,8 +67,12 @@ import java.util.regex.Pattern;
* <li>WebView/Chromium (the intended destination of this HTML) gracefully handles overlapping
* tags and usually renders the same result as spanned text in a TextView.
* </ul>
*
* @param text The (possibly span-styled) text to convert to HTML.
* @param displayDensity The screen density of the device. WebView treats 1 CSS px as one Android
* dp, so to convert size values from Android px to CSS px we need to know the screen density.
*/
public static String convert(@Nullable CharSequence text) {
public static String convert(@Nullable CharSequence text, float displayDensity) {
if (text == null) {
return "";
}
@ -75,7 +80,7 @@ import java.util.regex.Pattern;
return escapeHtml(text);
}
Spanned spanned = (Spanned) text;
SparseArray<Transition> spanTransitions = findSpanTransitions(spanned);
SparseArray<Transition> spanTransitions = findSpanTransitions(spanned, displayDensity);
StringBuilder html = new StringBuilder(spanned.length());
int previousTransition = 0;
@ -100,11 +105,12 @@ import java.util.regex.Pattern;
return html.toString();
}
private static SparseArray<Transition> findSpanTransitions(Spanned spanned) {
private static SparseArray<Transition> findSpanTransitions(
Spanned spanned, float displayDensity) {
SparseArray<Transition> spanTransitions = new SparseArray<>();
for (Object span : spanned.getSpans(0, spanned.length(), Object.class)) {
@Nullable String openingTag = getOpeningTag(span);
@Nullable String openingTag = getOpeningTag(span, displayDensity);
@Nullable String closingTag = getClosingTag(span);
int spanStart = spanned.getSpanStart(span);
int spanEnd = spanned.getSpanEnd(span);
@ -120,7 +126,7 @@ import java.util.regex.Pattern;
}
@Nullable
private static String getOpeningTag(Object span) {
private static String getOpeningTag(Object span, float displayDensity) {
if (span instanceof ForegroundColorSpan) {
ForegroundColorSpan colorSpan = (ForegroundColorSpan) span;
return Util.formatInvariant(
@ -132,6 +138,13 @@ import java.util.regex.Pattern;
HtmlUtils.toCssRgba(colorSpan.getBackgroundColor()));
} else if (span instanceof HorizontalTextInVerticalContextSpan) {
return "<span style='text-combine-upright:all;'>";
} else if (span instanceof AbsoluteSizeSpan) {
AbsoluteSizeSpan absoluteSizeSpan = (AbsoluteSizeSpan) span;
float sizeCssPx =
absoluteSizeSpan.getDip()
? absoluteSizeSpan.getSize()
: absoluteSizeSpan.getSize() / displayDensity;
return Util.formatInvariant("<span style='font-size:%.2fpx;'>", sizeCssPx);
} else if (span instanceof TypefaceSpan) {
@Nullable String fontFamily = ((TypefaceSpan) span).getFamily();
return fontFamily != null
@ -171,7 +184,8 @@ import java.util.regex.Pattern;
private static String getClosingTag(Object span) {
if (span instanceof ForegroundColorSpan
|| span instanceof BackgroundColorSpan
|| span instanceof HorizontalTextInVerticalContextSpan) {
|| span instanceof HorizontalTextInVerticalContextSpan
|| span instanceof AbsoluteSizeSpan) {
return "</span>";
} else if (span instanceof TypefaceSpan) {
@Nullable String fontFamily = ((TypefaceSpan) span).getFamily();

View file

@ -309,7 +309,9 @@ import java.util.List;
horizontalTranslatePercent,
verticalTranslatePercent))
.append(Util.formatInvariant("<span style=\"background-color:%s;\">", backgroundColorCss))
.append(SpannedToHtmlConverter.convert(cue.text))
.append(
SpannedToHtmlConverter.convert(
cue.text, getContext().getResources().getDisplayMetrics().density))
.append("</span>")
.append("</div>");
}

View file

@ -22,21 +22,31 @@ import android.graphics.Color;
import android.graphics.Typeface;
import android.text.SpannableString;
import android.text.Spanned;
import android.text.style.AbsoluteSizeSpan;
import android.text.style.BackgroundColorSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
import android.text.style.TypefaceSpan;
import android.text.style.UnderlineSpan;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.text.span.HorizontalTextInVerticalContextSpan;
import com.google.android.exoplayer2.text.span.RubySpan;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
/** Tests for {@link SpannedToHtmlConverter}. */
@RunWith(AndroidJUnit4.class)
public class SpannedToHtmlConverterTest {
private final float displayDensity;
public SpannedToHtmlConverterTest() {
displayDensity =
ApplicationProvider.getApplicationContext().getResources().getDisplayMetrics().density;
}
@Test
public void convert_supportsForegroundColorSpan() {
SpannableString spanned = new SpannableString("String with colored section");
@ -46,7 +56,7 @@ public class SpannedToHtmlConverterTest {
"String with colored".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
String html = SpannedToHtmlConverter.convert(spanned);
String html = SpannedToHtmlConverter.convert(spanned, displayDensity);
assertThat(html)
.isEqualTo("String with <span style='color:rgba(64,32,16,0.200);'>colored</span> section");
@ -61,7 +71,7 @@ public class SpannedToHtmlConverterTest {
"String with highlighted".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
String html = SpannedToHtmlConverter.convert(spanned);
String html = SpannedToHtmlConverter.convert(spanned, displayDensity);
assertThat(html)
.isEqualTo(
@ -78,7 +88,7 @@ public class SpannedToHtmlConverterTest {
"Vertical text with 123".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
String html = SpannedToHtmlConverter.convert(spanned);
String html = SpannedToHtmlConverter.convert(spanned, displayDensity);
assertThat(html)
.isEqualTo(
@ -86,6 +96,40 @@ public class SpannedToHtmlConverterTest {
+ "horizontal numbers");
}
// Set the screen density so we see that px are handled differently to dp.
@Config(qualifiers = "xhdpi")
@Test
public void convert_supportsAbsoluteSizeSpan_px() {
SpannableString spanned = new SpannableString("String with 10px section");
spanned.setSpan(
new AbsoluteSizeSpan(/* size= */ 10, /* dip= */ false),
"String with ".length(),
"String with 10px".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
String html = SpannedToHtmlConverter.convert(spanned, displayDensity);
// 10 Android px are converted to 5 CSS px because WebView treats 1 CSS px as 1 Android dp
// and we're using screen density xhdpi i.e. density=2.
assertThat(html).isEqualTo("String with <span style='font-size:5.00px;'>10px</span> section");
}
// Set the screen density so we see that px are handled differently to dp.
@Config(qualifiers = "xhdpi")
@Test
public void convert_supportsAbsoluteSizeSpan_dp() {
SpannableString spanned = new SpannableString("String with 10dp section");
spanned.setSpan(
new AbsoluteSizeSpan(/* size= */ 10, /* dip= */ true),
"String with ".length(),
"String with 10dp".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
String html = SpannedToHtmlConverter.convert(spanned, displayDensity);
assertThat(html).isEqualTo("String with <span style='font-size:10.00px;'>10dp</span> section");
}
@Test
public void convert_supportsTypefaceSpan() {
SpannableString spanned = new SpannableString("String with Times New Roman section");
@ -95,7 +139,7 @@ public class SpannedToHtmlConverterTest {
"String with Times New Roman".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
String html = SpannedToHtmlConverter.convert(spanned);
String html = SpannedToHtmlConverter.convert(spanned, displayDensity);
assertThat(html)
.isEqualTo(
@ -112,7 +156,7 @@ public class SpannedToHtmlConverterTest {
"String with unstyled".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
String html = SpannedToHtmlConverter.convert(spanned);
String html = SpannedToHtmlConverter.convert(spanned, displayDensity);
assertThat(html).isEqualTo("String with unstyled section");
}
@ -137,7 +181,7 @@ public class SpannedToHtmlConverterTest {
"String with bold, italic and bold-italic".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
String html = SpannedToHtmlConverter.convert(spanned);
String html = SpannedToHtmlConverter.convert(spanned, displayDensity);
assertThat(html)
.isEqualTo(
@ -159,7 +203,7 @@ public class SpannedToHtmlConverterTest {
"String with over-annotated and under-annotated".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
String html = SpannedToHtmlConverter.convert(spanned);
String html = SpannedToHtmlConverter.convert(spanned, displayDensity);
assertThat(html)
.isEqualTo(
@ -185,28 +229,31 @@ public class SpannedToHtmlConverterTest {
"String with underlined".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
String html = SpannedToHtmlConverter.convert(spanned);
String html = SpannedToHtmlConverter.convert(spanned, displayDensity);
assertThat(html).isEqualTo("String with <u>underlined</u> section.");
}
@Test
public void convert_escapesHtmlInUnspannedString() {
String html = SpannedToHtmlConverter.convert("String with <b>bold</b> tags");
String html = SpannedToHtmlConverter.convert("String with <b>bold</b> tags", displayDensity);
assertThat(html).isEqualTo("String with &lt;b&gt;bold&lt;/b&gt; tags");
}
@Test
public void convert_handlesLinebreakInUnspannedString() {
String html = SpannedToHtmlConverter.convert("String with\nnew line and\r\ncrlf style too");
String html =
SpannedToHtmlConverter.convert(
"String with\nnew line and\r\ncrlf style too", displayDensity);
assertThat(html).isEqualTo("String with<br>new line and<br>crlf style too");
}
@Test
public void convert_doesntConvertAmpersandLineFeedToBrTag() {
String html = SpannedToHtmlConverter.convert("String with&#10;new line ampersand code");
String html =
SpannedToHtmlConverter.convert("String with&#10;new line ampersand code", displayDensity);
assertThat(html).isEqualTo("String with&amp;#10;new line ampersand code");
}
@ -220,14 +267,16 @@ public class SpannedToHtmlConverterTest {
"String with <foo>unrecognised</foo>".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
String html = SpannedToHtmlConverter.convert(spanned);
String html = SpannedToHtmlConverter.convert(spanned, displayDensity);
assertThat(html).isEqualTo("String with <i>&lt;foo&gt;unrecognised&lt;/foo&gt;</i> tags");
}
@Test
public void convert_handlesLinebreakInSpannedString() {
String html = SpannedToHtmlConverter.convert("String with\nnew line and\r\ncrlf style too");
String html =
SpannedToHtmlConverter.convert(
"String with\nnew line and\r\ncrlf style too", displayDensity);
assertThat(html).isEqualTo("String with<br>new line and<br>crlf style too");
}
@ -236,7 +285,7 @@ public class SpannedToHtmlConverterTest {
public void convert_convertsNonAsciiCharactersToAmpersandCodes() {
String html =
SpannedToHtmlConverter.convert(
new SpannableString("Strìng with 優しいの non-ASCII characters"));
new SpannableString("Strìng with 優しいの non-ASCII characters"), displayDensity);
assertThat(html)
.isEqualTo("Str&#236;ng with &#20778;&#12375;&#12356;&#12398; non-ASCII characters");
@ -256,7 +305,7 @@ public class SpannedToHtmlConverterTest {
"String with unrecognised".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
String html = SpannedToHtmlConverter.convert(spanned);
String html = SpannedToHtmlConverter.convert(spanned, displayDensity);
assertThat(html).isEqualTo("String with unrecognised span");
}
@ -270,7 +319,7 @@ public class SpannedToHtmlConverterTest {
spanned.setSpan(new StyleSpan(Typeface.ITALIC), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
spanned.setSpan(new UnderlineSpan(), start, end, Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
String html = SpannedToHtmlConverter.convert(spanned);
String html = SpannedToHtmlConverter.convert(spanned, displayDensity);
assertThat(html).isEqualTo("String with <b><i><u>italic-bold-underlined</u></i></b> section");
}
@ -287,7 +336,7 @@ public class SpannedToHtmlConverterTest {
"String with italic and bold".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
String html = SpannedToHtmlConverter.convert(spanned);
String html = SpannedToHtmlConverter.convert(spanned, displayDensity);
assertThat(html).isEqualTo("String with <i>italic and <b>bold</b></i> section");
}
@ -306,7 +355,7 @@ public class SpannedToHtmlConverterTest {
"String with italic and bold section".length(),
Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
String html = SpannedToHtmlConverter.convert(spanned);
String html = SpannedToHtmlConverter.convert(spanned, displayDensity);
assertThat(html).isEqualTo("<i>String with italic <b>and bold</i> section</b>");
}